apify-test-tools 0.4.0 → 0.5.1

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 (53) hide show
  1. package/.prettierrc +5 -0
  2. package/.vscode/settings.json +3 -0
  3. package/CHANGELOG.md +14 -0
  4. package/bin/git.ts +11 -16
  5. package/bin/github.ts +5 -14
  6. package/bin/main.ts +38 -38
  7. package/bin/slack.ts +13 -16
  8. package/dist/bin/git.d.ts +2 -2
  9. package/dist/bin/git.d.ts.map +1 -1
  10. package/dist/bin/git.js +3 -6
  11. package/dist/bin/git.js.map +1 -1
  12. package/dist/bin/github.d.ts +1 -1
  13. package/dist/bin/github.d.ts.map +1 -1
  14. package/dist/bin/github.js +3 -9
  15. package/dist/bin/github.js.map +1 -1
  16. package/dist/bin/main.js +5 -7
  17. package/dist/bin/main.js.map +1 -1
  18. package/dist/bin/slack.d.ts +2 -2
  19. package/dist/bin/slack.d.ts.map +1 -1
  20. package/dist/bin/slack.js +4 -2
  21. package/dist/bin/slack.js.map +1 -1
  22. package/dist/lib/consts.d.ts +1 -1
  23. package/dist/lib/consts.d.ts.map +1 -1
  24. package/dist/lib/consts.js +2 -4
  25. package/dist/lib/consts.js.map +1 -1
  26. package/dist/lib/extend-expect.d.ts.map +1 -1
  27. package/dist/lib/extend-expect.js +11 -0
  28. package/dist/lib/extend-expect.js.map +1 -1
  29. package/dist/lib/lib.d.ts +1 -1
  30. package/dist/lib/lib.d.ts.map +1 -1
  31. package/dist/lib/lib.js +10 -7
  32. package/dist/lib/lib.js.map +1 -1
  33. package/dist/lib/run-test-result.d.ts +1 -1
  34. package/dist/lib/run-test-result.d.ts.map +1 -1
  35. package/dist/lib/run-test-result.js +1 -0
  36. package/dist/lib/run-test-result.js.map +1 -1
  37. package/dist/lib/types.d.ts +4 -0
  38. package/dist/lib/types.d.ts.map +1 -1
  39. package/dist/test/unit/custom-matchers.test.js +2 -2
  40. package/dist/test/unit/custom-matchers.test.js.map +1 -1
  41. package/dist/test/unit/parse-commit.test.js +5 -5
  42. package/dist/test/unit/parse-commit.test.js.map +1 -1
  43. package/dist/tsconfig.tsbuildinfo +1 -1
  44. package/lib/consts.ts +3 -5
  45. package/lib/extend-expect.ts +37 -22
  46. package/lib/lib.ts +29 -40
  47. package/lib/run-test-result.ts +9 -10
  48. package/lib/types.ts +70 -67
  49. package/package.json +4 -1
  50. package/test/unit/custom-matchers.test.ts +6 -9
  51. package/test/unit/parse-commit.test.ts +6 -7
  52. package/tsconfig.json +4 -8
  53. package/test/unit/test-actor.test.ts +0 -17
package/lib/consts.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ToFinishWithOptionsWithDefaults } from './types';
1
+ import type { ToFinishWithOptionsWithDefaults } from './types.js';
2
2
 
3
3
  export const TO_FINISH_WITH_OPTIONS: ToFinishWithOptionsWithDefaults = {
4
4
  status: 'SUCCEEDED',
@@ -8,8 +8,6 @@ export const TO_FINISH_WITH_OPTIONS: ToFinishWithOptionsWithDefaults = {
8
8
  },
9
9
  failedRequests: 0,
10
10
  requestsRetries: { max: 3 },
11
- forbiddenLogs: [
12
- 'ReferenceError',
13
- 'TypeError',
14
- ],
11
+ maxRetriesPerRequest: null,
12
+ forbiddenLogs: ['ReferenceError', 'TypeError'],
15
13
  };
@@ -1,7 +1,7 @@
1
1
  import type { Assertion, ExpectStatic } from 'vitest';
2
2
 
3
3
  import { TO_FINISH_WITH_OPTIONS } from './consts.js';
4
- import type { Interval, ToFinishWithOptions } from './types';
4
+ import type { Interval, ToFinishWithOptions } from './types.js';
5
5
  import { RunTestResult } from './run-test-result.js';
6
6
 
7
7
  export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
@@ -110,7 +110,7 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
110
110
  },
111
111
  toFinishWith: async <PpeEvent extends string>(
112
112
  received: RunTestResult,
113
- userOptions: ToFinishWithOptions<PpeEvent>,
113
+ userOptions: ToFinishWithOptions<PpeEvent>
114
114
  ) => {
115
115
  const options = {
116
116
  ...TO_FINISH_WITH_OPTIONS,
@@ -146,11 +146,13 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
146
146
  const checkInterval = (
147
147
  value: number | undefined,
148
148
  key: ['datasetItemCount', 'duration', 'failedRequests', 'requestsRetries'][number],
149
- label: string,
149
+ label: string
150
150
  ) => {
151
151
  const result = isWithinInterval(diffs, value, options, key);
152
152
  if (result === false) {
153
- failedAssertions.push(`Failed ${label} check, expected ${JSON.stringify(options[key])}, got ${value}.`);
153
+ failedAssertions.push(
154
+ `Failed ${label} check, expected ${JSON.stringify(options[key])}, got ${value}.`
155
+ );
154
156
  }
155
157
  };
156
158
 
@@ -159,6 +161,19 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
159
161
  checkInterval(stats?.requestsFailed, 'failedRequests', 'failed requests');
160
162
  checkInterval(stats?.requestsRetries, 'requestsRetries', 'requests retries');
161
163
 
164
+ {
165
+ if (options.maxRetriesPerRequest !== null) {
166
+ const maxRetriesPerRequestObserved = (stats?.requestRetryHistogram ?? [0]).length - 1;
167
+ if (maxRetriesPerRequestObserved > options.maxRetriesPerRequest) {
168
+ diffs.pass = false;
169
+ diffs.actual.push(`maxRetriesPerRequest=${maxRetriesPerRequestObserved}`);
170
+ diffs.expected.push(`maxRetriesPerRequest<=${options.maxRetriesPerRequest}`);
171
+ failedAssertions.push(
172
+ `Failed max retries observed check, expected <=${options.maxRetriesPerRequest}, got ${maxRetriesPerRequestObserved}.`
173
+ );
174
+ }
175
+ }
176
+ }
162
177
  const ppeDiffs: Diffs = {
163
178
  pass: true,
164
179
  actual: ['PPE Events:'],
@@ -175,12 +190,7 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
175
190
 
176
191
  for (const ppeEvent of uniquePpeEvents) {
177
192
  if (!(ppeEvent in expected)) {
178
- isWithinInterval(
179
- ppeDiffs,
180
- actual[ppeEvent],
181
- { [ppeEvent]: 0 },
182
- ppeEvent,
183
- );
193
+ isWithinInterval(ppeDiffs, actual[ppeEvent], { [ppeEvent]: 0 }, ppeEvent);
184
194
  continue;
185
195
  }
186
196
  if (!(ppeEvent in actual)) {
@@ -196,7 +206,11 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
196
206
  diffs.pass = ppeDiffs.pass;
197
207
  diffs.actual.push(ppeDiffs.actual.join('\n '));
198
208
  diffs.expected.push(ppeDiffs.expected.join('\n '));
199
- failedAssertions.push(`Failed PPE event counts check, expected ${JSON.stringify(options.chargedEventCounts)}, got ${JSON.stringify(chargedEventCounts)}.`);
209
+ failedAssertions.push(
210
+ `Failed PPE event counts check, expected ${JSON.stringify(
211
+ options.chargedEventCounts
212
+ )}, got ${JSON.stringify(chargedEventCounts)}.`
213
+ );
200
214
  }
201
215
  }
202
216
 
@@ -213,13 +227,15 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
213
227
  diffs.pass = false;
214
228
  diffs.actual.push(` logs=[${occuredLogs.join(', ')}]`);
215
229
  diffs.expected.push(` logs=[]`);
216
- failedAssertions.push(`Failed forbidden logs check, expected [] but got [${occuredLogs.join(', ')}].`);
230
+ failedAssertions.push(
231
+ `Failed forbidden logs check, expected [] but got [${occuredLogs.join(', ')}].`
232
+ );
217
233
  }
218
234
  }
219
235
 
220
236
  return {
221
237
  pass: diffs.pass,
222
- message: () => `Run did not finish as expected. Failed assertions: ${failedAssertions.join(' ',)}`,
238
+ message: () => `Run did not finish as expected. Failed assertions: ${failedAssertions.join(' ')}`,
223
239
  actual: diffs.actual.join('\n '),
224
240
  expected: diffs.expected.join('\n '),
225
241
  };
@@ -255,17 +271,16 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
255
271
  };
256
272
 
257
273
  type Diffs = {
258
- pass: boolean
259
- actual: string[]
260
- expected: string[]
261
-
262
- }
274
+ pass: boolean;
275
+ actual: string[];
276
+ expected: string[];
277
+ };
263
278
 
264
279
  const isWithinInterval = <T extends string>(
265
280
  diffs: Diffs,
266
281
  actual: number | undefined,
267
282
  options: Record<T, Interval | null>,
268
- intervalOption: T,
283
+ intervalOption: T
269
284
  ) => {
270
285
  const expected = options[intervalOption];
271
286
  if (expected === null) {
@@ -277,7 +292,7 @@ const isWithinInterval = <T extends string>(
277
292
  diffs.pass = false;
278
293
  diffs.actual.push(`${intervalOption}=${actual}`);
279
294
  diffs.expected.push(`${intervalOption}=${expected}`);
280
- return false
295
+ return false;
281
296
  }
282
297
  } else if (typeof expected === 'object') {
283
298
  const { min, max } = expected;
@@ -285,8 +300,8 @@ const isWithinInterval = <T extends string>(
285
300
  diffs.pass = false;
286
301
  diffs.actual.push(`${intervalOption}=${actual}`);
287
302
  diffs.expected.push(`${intervalOption}=<${min ?? ''},${max ?? ''}>`);
288
- return false
303
+ return false;
289
304
  }
290
305
  }
291
- return
306
+ return;
292
307
  };
package/lib/lib.ts CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  SuiteFactory,
8
8
  TestContext,
9
9
  } from 'vitest';
10
- import type { ActorBuild, ActorTestOptions, RunOptions } from './types';
10
+ import type { ActorBuild, ActorTestOptions, RunOptions } from './types.js';
11
11
  import { RunTestResult } from './run-test-result.js';
12
12
  import { extendExpect } from './extend-expect.js';
13
13
  import { getActorPrefilledInput, sleep } from './utils.js';
@@ -26,7 +26,7 @@ try {
26
26
  throw new Error(`Failed to parse actor builds: ${err}`);
27
27
  }
28
28
 
29
- const config = actorBuilds.reduce((map, cfg) => {
29
+ const config = actorBuilds.reduce<Map<string, ActorBuild>>((map, cfg) => {
30
30
  map.set(cfg.actorName, cfg);
31
31
  map.set(cfg.actorId, cfg);
32
32
  return map;
@@ -44,11 +44,7 @@ const DEFAULT_TEST_OPTIONS: ActorTestOptions = {
44
44
  timeout: 60_000 * 60,
45
45
  };
46
46
 
47
- export const describe = (
48
- name: string,
49
- fn?: SuiteFactory<object>,
50
- options: ActorTestOptions = DEFAULT_TEST_OPTIONS,
51
- ) => {
47
+ export const describe = (name: string, fn?: SuiteFactory<object>, options: ActorTestOptions = DEFAULT_TEST_OPTIONS) => {
52
48
  vitestDescribe.runIf(!!RUN_PLATFORM_TESTS || !!RUN_ALL_PLATFORM_TESTS)(name, options, fn);
53
49
  };
54
50
 
@@ -60,7 +56,7 @@ export const testActor = <T>(
60
56
  actorName: string,
61
57
  testName: string,
62
58
  fn: TestFunction<{ run: ReturnType<typeof createStartRunFn<T>> }>,
63
- testOptions?: ActorTestOptions,
59
+ testOptions?: ActorTestOptions
64
60
  ) => {
65
61
  const options = {
66
62
  ...DEFAULT_TEST_ACTOR_OPTIONS,
@@ -68,8 +64,7 @@ export const testActor = <T>(
68
64
  };
69
65
  const name = `${actorName}: ${testName}`;
70
66
  const shouldRun = !!RUN_ALL_PLATFORM_TESTS || config.has(actorName);
71
-
72
- vitestTest.runIf(shouldRun)(name, options, async (context) => {
67
+ vitestTest.runIf(shouldRun)(name, options, async <T extends TestContext>(context: T) => {
73
68
  const { expect, ...rest } = context;
74
69
  await fn({
75
70
  expect: extendExpect(expect),
@@ -89,7 +84,7 @@ export const testStandbyActor = <I = any, O = any>(
89
84
  actorName: string,
90
85
  testName: string,
91
86
  fn: TestFunction<{ callStandby: ReturnType<typeof createStartStandbyFn<I, O>> }>,
92
- testOptions?: ActorTestOptions,
87
+ testOptions?: ActorTestOptions
93
88
  ) => {
94
89
  const options = {
95
90
  ...DEFAULT_TEST_ACTOR_OPTIONS,
@@ -98,7 +93,7 @@ export const testStandbyActor = <I = any, O = any>(
98
93
  const name = `${actorName}: ${testName}`;
99
94
  const shouldRun = !!RUN_ALL_PLATFORM_TESTS || config.has(actorName);
100
95
 
101
- vitestTest.runIf(shouldRun)(name, options, async (context) => {
96
+ vitestTest.runIf(shouldRun)(name, options, async <T extends TestContext>(context: T) => {
102
97
  const standbyTask = await createStandbyTask(actorName, config.get(actorName)?.buildNumber);
103
98
  const { annotate } = context;
104
99
  const { expect, ...rest } = context;
@@ -113,29 +108,28 @@ export const testStandbyActor = <I = any, O = any>(
113
108
  } catch {}
114
109
 
115
110
  const { taskId } = standbyTask;
116
- const runs = (await apifyClient.task(taskId).runs().list()).items
111
+ const runs = (await apifyClient.task(taskId).runs().list()).items;
117
112
  for (const run of runs) {
118
113
  const runLink = generateRunLink(run);
119
114
  await annotate(runLink, 'run_link');
120
115
  }
121
116
 
122
117
  if (taskId) {
123
- await apifyClient.task(taskId).delete()
118
+ await apifyClient.task(taskId).delete();
124
119
  }
125
-
126
120
  });
127
121
  };
128
122
 
129
123
  export const testTestActor = <T>(
130
124
  testName: string,
131
- fn: TestFunction<{ run: ReturnType<typeof createStartRunFn<T>> }>,
125
+ fn: TestFunction<{ run: ReturnType<typeof createStartRunFn<T>> }>
132
126
  ) => {
133
127
  vitestTest(testName, async (context) => {
134
128
  const { expect, ...rest } = context;
135
129
  await fn({
136
130
  expect: extendExpect(expect),
137
131
  // @ts-expect-error: this just to test custom matchers
138
- run: () => { },
132
+ run: () => {},
139
133
  ...rest,
140
134
  });
141
135
  });
@@ -158,14 +152,14 @@ const createStartStandbyFn = <I, O>(standbyTask: StandbyTask) => {
158
152
  body: JSON.stringify(input),
159
153
  });
160
154
 
161
- const data = await response.json() as O;
155
+ const data = (await response.json()) as O;
162
156
  return {
163
157
  data,
164
158
  status: response.status,
165
159
  headers: response.headers,
166
160
  };
167
161
  };
168
- }
162
+ };
169
163
 
170
164
  interface StandbyTask {
171
165
  standbyUrl: string;
@@ -174,7 +168,7 @@ interface StandbyTask {
174
168
 
175
169
  const randomInt = (min: number, max: number) => {
176
170
  return Math.floor(Math.random() * (max - min + 1)) + min;
177
- }
171
+ };
178
172
 
179
173
  /**
180
174
  * Creates a task with specific `build` - either `buildNumber` or default.
@@ -202,19 +196,22 @@ const createStandbyTask = async (actorNameOrId: string, buildNumber?: string): P
202
196
  const actorStandbyOptions: ActorStandby = {
203
197
  ...defaultActorStandby,
204
198
  build,
205
- }
199
+ };
206
200
 
207
201
  try {
208
202
  const title = `Test task - ${build}:${actorNameOrId}`.slice(0, 62);
209
203
  // we try to create unique task name containing only `a-z0-9-` characters and at most 63 characters long
210
- const name = `${randomInt(1, 1_000_000)}${title.toLowerCase().replaceAll(/\s+/g, '').replaceAll(/[^a-z0-9-]+/g, '-')}`.slice(0, 62);
211
- const newTask = await apifyClient.tasks().create({
204
+ const name = `${randomInt(1, 1_000_000)}${title
205
+ .toLowerCase()
206
+ .replaceAll(/\s+/g, '')
207
+ .replaceAll(/[^a-z0-9-]+/g, '-')}`.slice(0, 62);
208
+ const newTask = (await apifyClient.tasks().create({
212
209
  actId: actorNameOrId,
213
210
  actorStandby: actorStandbyOptions,
214
211
  description: `Task for testing standby version ${build}`,
215
212
  title,
216
213
  name,
217
- }) as Task & { standbyUrl?: string };
214
+ })) as Task & { standbyUrl?: string };
218
215
 
219
216
  const { id, standbyUrl } = newTask;
220
217
 
@@ -225,11 +222,11 @@ const createStandbyTask = async (actorNameOrId: string, buildNumber?: string): P
225
222
  return {
226
223
  standbyUrl,
227
224
  taskId: id,
228
- }
225
+ };
229
226
  } catch (error) {
230
227
  throw new Error(`Failed to create task: ${error}`);
231
228
  }
232
- }
229
+ };
233
230
 
234
231
  const createStartRunFn = <T>(actorNameOrId: string, testContext: TestContext) => {
235
232
  const { annotate, task } = testContext;
@@ -237,31 +234,23 @@ const createStartRunFn = <T>(actorNameOrId: string, testContext: TestContext) =>
237
234
  const build = actorConfig?.buildNumber;
238
235
  const buildId = actorConfig?.buildId;
239
236
  return async (runOptions: RunOptions<T>) => {
240
- const {
241
- input,
242
- options,
243
- prefilledInput,
244
- runId,
245
- } = runOptions;
237
+ const { input, options, prefilledInput, runId } = runOptions;
246
238
 
247
239
  if (runId) {
248
- const run = await apifyClient.run(runId).get()
240
+ const run = await apifyClient.run(runId).get();
249
241
  if (!run) {
250
242
  throw new Error(`Run with id "${runId}" doesn't exist`);
251
243
  }
252
- return new RunTestResult(apifyClient, run)
244
+ return new RunTestResult(apifyClient, run);
253
245
  }
254
246
 
255
247
  const actor = apifyClient.actor(actorNameOrId);
256
248
 
257
249
  const actorInput = {
258
- ...(prefilledInput && await getActorPrefilledInput(apifyClient, actorNameOrId, buildId)),
250
+ ...(prefilledInput && (await getActorPrefilledInput(apifyClient, actorNameOrId, buildId))),
259
251
  ...input,
260
252
  };
261
- const run = await actor.call(
262
- actorInput,
263
- { build, ...options, },
264
- );
253
+ const run = await actor.call(actorInput, { build, ...options });
265
254
 
266
255
  const runLink = generateRunLink(run);
267
256
  await annotate(runLink, 'run_link');
@@ -281,4 +270,4 @@ const createStartRunFn = <T>(actorNameOrId: string, testContext: TestContext) =>
281
270
 
282
271
  const generateRunLink = (run: ActorRun | ActorRunListItem): string => {
283
272
  return `https://console.apify.com/view/runs/${run.id}`;
284
- }
273
+ };
@@ -1,5 +1,5 @@
1
1
  import { ActorRun, ApifyClient, KeyValueStoreClient } from 'apify-client';
2
- import type { Dataset, SdkCrawlerStatistics } from './types';
2
+ import type { Dataset, SdkCrawlerStatistics } from './types.js';
3
3
 
4
4
  export class RunTestResult {
5
5
  private log: string | undefined;
@@ -9,10 +9,9 @@ export class RunTestResult {
9
9
  private runInfo: ActorRun | undefined;
10
10
  private input: unknown | undefined;
11
11
 
12
- constructor(
13
- private readonly apifyClient: ApifyClient,
14
- private readonly run: ActorRun,
15
- ) { /**/ }
12
+ constructor(private readonly apifyClient: ApifyClient, private readonly run: ActorRun) {
13
+ /**/
14
+ }
16
15
 
17
16
  getStatistics = async (): Promise<SdkCrawlerStatistics | undefined> => {
18
17
  if (this.statistics) {
@@ -33,7 +32,7 @@ export class RunTestResult {
33
32
  return runLog as string;
34
33
  };
35
34
 
36
- getDataset = async<T>(): Promise<Dataset<T>> => {
35
+ getDataset = async <T>(): Promise<Dataset<T>> => {
37
36
  if (this.dataset) {
38
37
  return this.dataset as Dataset<T>;
39
38
  }
@@ -49,18 +48,18 @@ export class RunTestResult {
49
48
  }
50
49
  const keyValueStoreClient = this.apifyClient.keyValueStore(this.run.defaultKeyValueStoreId);
51
50
  this.keyValueStoreClient = keyValueStoreClient;
52
- return keyValueStoreClient
51
+ return keyValueStoreClient;
53
52
  };
54
53
 
55
- getInput = async<T>(): Promise<T> => {
54
+ getInput = async <T>(): Promise<T> => {
56
55
  if (this.input) {
57
- return this.input as T
56
+ return this.input as T;
58
57
  }
59
58
  const kvs = this.apifyClient.keyValueStore(this.run.defaultKeyValueStoreId);
60
59
  const input = await kvs.getRecord('INPUT');
61
60
  this.input = input?.value;
62
61
  return this.input as T;
63
- }
62
+ };
64
63
 
65
64
  getRunInfo = async (): Promise<ActorRun> => {
66
65
  if (this.runInfo) {
package/lib/types.ts CHANGED
@@ -6,57 +6,57 @@ export type ActorBuild = {
6
6
  actorId: string;
7
7
  buildNumber: string;
8
8
  actorName: string;
9
- }
9
+ };
10
10
 
11
11
  export type RunOptions<T> = {
12
- input: Omit<T, 'actorName'>
13
- options?: ActorCallOptions
14
- prefilledInput?: boolean
12
+ input: Omit<T, 'actorName'>;
13
+ options?: ActorCallOptions;
14
+ prefilledInput?: boolean;
15
15
  /**
16
16
  * If you specify `runId`, all the other options will be ignored and this run's data will
17
17
  * be downloaded instead.
18
18
  *
19
19
  * This is usefull for testing your tests on existing runs
20
20
  */
21
- runId?: string
22
- }
21
+ runId?: string;
22
+ };
23
23
 
24
24
  export type Dataset<T> = {
25
- items: T[]
26
- }
25
+ items: T[];
26
+ };
27
27
 
28
- export type OpenInterval = {
29
- min: number
30
- max?: undefined
31
- } | {
32
- min?: undefined
33
- max: number
34
- }
28
+ export type OpenInterval =
29
+ | {
30
+ min: number;
31
+ max?: undefined;
32
+ }
33
+ | {
34
+ min?: undefined;
35
+ max: number;
36
+ };
35
37
 
36
38
  export type ClosedInterval = {
37
- min: number
38
- max: number
39
- }
39
+ min: number;
40
+ max: number;
41
+ };
40
42
 
41
- export type Interval = number | OpenInterval | ClosedInterval
43
+ export type Interval = number | OpenInterval | ClosedInterval;
42
44
 
43
45
  export type ToFinishWithOptionsWithDefaults = {
44
- status: RunStatus
45
- duration: Interval | null
46
- failedRequests: Interval | null
47
- requestsRetries: Interval | null
48
- forbiddenLogs: string[]
49
- }
46
+ status: RunStatus;
47
+ duration: Interval | null;
48
+ failedRequests: Interval | null;
49
+ requestsRetries: Interval | null;
50
+ forbiddenLogs: string[];
51
+ maxRetriesPerRequest: number | null;
52
+ };
50
53
  export type IntervalOption<PpeEvent extends string> = keyof Pick<
51
54
  ToFinishWithOptions<PpeEvent>,
52
- | 'datasetItemCount'
53
- | 'requestsRetries'
54
- | 'failedRequests'
55
- | 'duration'
56
- >
55
+ 'datasetItemCount' | 'requestsRetries' | 'failedRequests' | 'duration'
56
+ >;
57
57
 
58
58
  export type ToFinishWithOptions<PpeEvent extends string> = Partial<ToFinishWithOptionsWithDefaults> & {
59
- datasetItemCount: Interval
59
+ datasetItemCount: Interval;
60
60
  /**
61
61
  * Define expected charged (PPE) event counts. You can also define count as `Interval`.
62
62
  *
@@ -71,26 +71,26 @@ export type ToFinishWithOptions<PpeEvent extends string> = Partial<ToFinishWithO
71
71
  * If you omit any PPE event, it's expected count will be 0.
72
72
  * Assertion will fail if `chargedEventCounts` doesn't contain some of the expected events.
73
73
  */
74
- chargedEventCounts?: Record<PpeEvent, Interval>
74
+ chargedEventCounts?: Record<PpeEvent, Interval>;
75
75
  };
76
76
 
77
77
  export type SdkCrawlerStatistics = {
78
- requestsFinished: number
79
- requestsFailed: number
80
- requestsRetries: number
81
- requestsFailedPerMinute: number
82
- requestsFinishedPerMinute: number
83
- requestMinDurationMillis: number
84
- requestMaxDurationMillis: number
85
- requestRetryHistogram: number[]
86
- requestTotalFailedDurationMillis: number
87
- requestTotalFinishedDurationMillis: number
88
- crawlerStartedAt: string
89
- crawlerFinishedAt: string
90
- statsPersistedAt: string
91
- crawlerRuntimeMillis: number
92
- crawlerLastStartTimestamp: number
93
- }
78
+ requestsFinished: number;
79
+ requestsFailed: number;
80
+ requestsRetries: number;
81
+ requestsFailedPerMinute: number;
82
+ requestsFinishedPerMinute: number;
83
+ requestMinDurationMillis: number;
84
+ requestMaxDurationMillis: number;
85
+ requestRetryHistogram: number[];
86
+ requestTotalFailedDurationMillis: number;
87
+ requestTotalFinishedDurationMillis: number;
88
+ crawlerStartedAt: string;
89
+ crawlerFinishedAt: string;
90
+ statsPersistedAt: string;
91
+ crawlerRuntimeMillis: number;
92
+ crawlerLastStartTimestamp: number;
93
+ };
94
94
 
95
95
  export type RunStatus =
96
96
  | 'SUCCEEDED'
@@ -100,22 +100,22 @@ export type RunStatus =
100
100
  | 'ABORTING'
101
101
  | 'ABORTED'
102
102
  | 'TIMING-OUT'
103
- | 'TIMED-OUT'
103
+ | 'TIMED-OUT';
104
104
 
105
105
  export interface ActorMatchers<R = unknown> {
106
- toBeArray: () => R
107
- toBeBoolean: () => R
108
- toBeEmptyArray: () => R
109
- toBeNonEmptyArray: () => R
110
- toBeNonEmptyString: () => R
111
- toBeNumber: () => R
112
- toBeFalse: () => R
113
- toBeTrue: () => R
114
- toBeNonEmptyObject: () => R
115
- toBeObject: () => R
116
- toBeString: () => R
117
- toBeWholeNumber: () => R
118
- toBeWithinRange: (lower: number, upper: number) => R
106
+ toBeArray: () => R;
107
+ toBeBoolean: () => R;
108
+ toBeEmptyArray: () => R;
109
+ toBeNonEmptyArray: () => R;
110
+ toBeNonEmptyString: () => R;
111
+ toBeNumber: () => R;
112
+ toBeFalse: () => R;
113
+ toBeTrue: () => R;
114
+ toBeNonEmptyObject: () => R;
115
+ toBeObject: () => R;
116
+ toBeString: () => R;
117
+ toBeWholeNumber: () => R;
118
+ toBeWithinRange: (lower: number, upper: number) => R;
119
119
  /**
120
120
  * Validates the following properties of a run:
121
121
  * - `status` (default: `SUCCEEDED`)
@@ -123,12 +123,13 @@ export interface ActorMatchers<R = unknown> {
123
123
  * - `failedRequests` (default: `0`)
124
124
  * - `requestsRetries` (default: `{ max: 3 }`)
125
125
  * - `forbiddenLogs` (default: `['ReferenceError', 'TypeError']`)
126
+ * - `maxRetriesPerRequest` (not checked by default)
126
127
  * - `datasetItemCount` (required)
127
128
  * - `chargedEventCounts`
128
129
  */
129
- toFinishWith: <PpeEvent extends string>(options: ToFinishWithOptions<PpeEvent>) => Promise<R>
130
- toStartWith: (prefix: string) => R
131
- hard: <T>(actual: T, message?: string) => Assertion
130
+ toFinishWith: <PpeEvent extends string>(options: ToFinishWithOptions<PpeEvent>) => Promise<R>;
131
+ toStartWith: (prefix: string) => R;
132
+ hard: <T>(actual: T, message?: string) => Assertion;
132
133
  }
133
134
 
134
135
  export type ActorTestOptions = Omit<TestOptions, 'retry'> & {
@@ -144,6 +145,8 @@ export type ActorTestOptions = Omit<TestOptions, 'retry'> & {
144
145
 
145
146
  declare module 'vitest' {
146
147
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
147
- interface Assertion<T = any> extends ActorMatchers<T> { }
148
- interface AsymmetricMatchersContaining extends ActorMatchers { }
148
+ interface Assertion<T = any> extends ActorMatchers<T> {}
149
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
150
+ interface Matchers<T = any> extends ActorMatchers<T> {}
151
+ interface AsymmetricMatchersContaining extends ActorMatchers {}
149
152
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apify-test-tools",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "type": "module",
5
5
  "description": "TBD",
6
6
  "engines": {
@@ -28,6 +28,9 @@
28
28
  "typescript": "^5.7",
29
29
  "vitest": "^3.2.4"
30
30
  },
31
+ "peerDependencies": {
32
+ "vitest": ">=3.2.4"
33
+ },
31
34
  "scripts": {
32
35
  "start": "npm run dist/bin/main.js",
33
36
  "start:dev": "GITHUB_WORKSPACE=local-clone tsx bin/main.ts",
@@ -2,13 +2,10 @@ import process from 'process';
2
2
  import { describe } from 'vitest';
3
3
 
4
4
  import { ApifyClient } from 'apify-client';
5
- import { testStandbyActor, testTestActor } from '../../lib/lib';
6
- import { RunTestResult } from '../../lib/run-test-result';
5
+ import { testStandbyActor, testTestActor } from '../../lib/lib.js';
6
+ import { RunTestResult } from '../../lib/run-test-result.js';
7
7
 
8
- type PpeEventType =
9
- | 'actor-start'
10
- | 'search-page-scraped'
11
- | 'ads-scraped'
8
+ type PpeEventType = 'actor-start' | 'search-page-scraped' | 'ads-scraped';
12
9
  enum PpeEventEnum {
13
10
  ACTOR_START = 'actor-start',
14
11
  SEARCH_PAGE_SCRAPED = 'search-page-scraped',
@@ -45,7 +42,7 @@ describe('custom-matchers', { timeout: 100_000 }, () => {
45
42
  },
46
43
  });
47
44
 
48
- await expect(runResult).toFinishWith<typeof PPE_EVENT_CONST[keyof typeof PPE_EVENT_CONST]>({
45
+ await expect(runResult).toFinishWith<(typeof PPE_EVENT_CONST)[keyof typeof PPE_EVENT_CONST]>({
49
46
  datasetItemCount: { min: 1, max: 20 },
50
47
  chargedEventCounts: {
51
48
  'actor-start': 1,
@@ -67,7 +64,7 @@ describe('custom-matchers', { timeout: 100_000 }, () => {
67
64
  maxRequests: 3,
68
65
  aggregateContacts: true,
69
66
  },
70
- })
67
+ });
71
68
  expect(status).toBe(200);
72
69
  expect(data[0].domain).toEqual('apify.com');
73
70
  }
@@ -78,7 +75,7 @@ describe('custom-matchers', { timeout: 100_000 }, () => {
78
75
  maxRequests: 3,
79
76
  aggregateContacts: true,
80
77
  },
81
- })
78
+ });
82
79
  expect(status).toBe(200);
83
80
  expect(data[0].domain).toEqual('apify.com');
84
81
  });