apify-test-tools 0.5.0 → 0.5.2
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/.prettierrc +5 -0
- package/.vscode/settings.json +3 -0
- package/CHANGELOG.md +10 -0
- package/bin/git.ts +24 -44
- package/bin/github.ts +5 -14
- package/bin/main.ts +38 -38
- package/bin/slack.ts +13 -16
- package/dist/bin/git.d.ts +9 -4
- package/dist/bin/git.d.ts.map +1 -1
- package/dist/bin/git.js +15 -36
- package/dist/bin/git.js.map +1 -1
- package/dist/bin/github.d.ts +1 -1
- package/dist/bin/github.d.ts.map +1 -1
- package/dist/bin/github.js +3 -9
- package/dist/bin/github.js.map +1 -1
- package/dist/bin/main.js +5 -7
- package/dist/bin/main.js.map +1 -1
- package/dist/bin/slack.d.ts +2 -2
- package/dist/bin/slack.d.ts.map +1 -1
- package/dist/bin/slack.js +4 -2
- package/dist/bin/slack.js.map +1 -1
- package/dist/lib/consts.d.ts +1 -1
- package/dist/lib/consts.d.ts.map +1 -1
- package/dist/lib/consts.js +1 -4
- package/dist/lib/consts.js.map +1 -1
- package/dist/lib/extend-expect.d.ts.map +1 -1
- package/dist/lib/extend-expect.js +11 -2
- package/dist/lib/extend-expect.js.map +1 -1
- package/dist/lib/lib.d.ts +1 -1
- package/dist/lib/lib.d.ts.map +1 -1
- package/dist/lib/lib.js +12 -9
- package/dist/lib/lib.js.map +1 -1
- package/dist/lib/run-test-result.d.ts +1 -1
- package/dist/lib/run-test-result.d.ts.map +1 -1
- package/dist/lib/run-test-result.js +1 -0
- package/dist/lib/run-test-result.js.map +1 -1
- package/dist/lib/types.d.ts +2 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/test/unit/bin/git.test.d.ts +2 -0
- package/dist/test/unit/bin/git.test.d.ts.map +1 -0
- package/dist/test/unit/bin/git.test.js +81 -0
- package/dist/test/unit/bin/git.test.js.map +1 -0
- package/dist/test/unit/custom-matchers.test.js +2 -2
- package/dist/test/unit/custom-matchers.test.js.map +1 -1
- package/dist/test/unit/parse-commit.test.js +5 -5
- package/dist/test/unit/parse-commit.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/lib/consts.ts +2 -5
- package/lib/extend-expect.ts +38 -25
- package/lib/lib.ts +31 -42
- package/lib/run-test-result.ts +9 -10
- package/lib/types.ts +69 -68
- package/package.json +4 -1
- package/test/unit/bin/git.test.ts +109 -0
- package/test/unit/custom-matchers.test.ts +6 -9
- package/test/unit/parse-commit.test.ts +6 -7
- package/tsconfig.json +4 -8
- 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,9 +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
|
-
],
|
|
15
11
|
maxRetriesPerRequest: null,
|
|
12
|
+
forbiddenLogs: ['ReferenceError', 'TypeError'],
|
|
16
13
|
};
|
package/lib/extend-expect.ts
CHANGED
|
@@ -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,
|
|
@@ -145,12 +145,14 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
|
|
|
145
145
|
|
|
146
146
|
const checkInterval = (
|
|
147
147
|
value: number | undefined,
|
|
148
|
-
key: ['datasetItemCount', 'duration', 'failedRequests', 'requestsRetries'
|
|
149
|
-
label: string
|
|
148
|
+
key: ['datasetItemCount', 'duration', 'failedRequests', 'requestsRetries'][number],
|
|
149
|
+
label: string
|
|
150
150
|
) => {
|
|
151
151
|
const result = isWithinInterval(diffs, value, options, key);
|
|
152
152
|
if (result === false) {
|
|
153
|
-
failedAssertions.push(
|
|
153
|
+
failedAssertions.push(
|
|
154
|
+
`Failed ${label} check, expected ${JSON.stringify(options[key])}, got ${value}.`
|
|
155
|
+
);
|
|
154
156
|
}
|
|
155
157
|
};
|
|
156
158
|
|
|
@@ -158,9 +160,20 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
|
|
|
158
160
|
checkInterval(durationMillis, 'duration', 'duration');
|
|
159
161
|
checkInterval(stats?.requestsFailed, 'failedRequests', 'failed requests');
|
|
160
162
|
checkInterval(stats?.requestsRetries, 'requestsRetries', 'requests retries');
|
|
161
|
-
const maxRetriesObserved = (stats?.requestRetryHistogram ?? [0]).length - 1;
|
|
162
|
-
checkInterval(maxRetriesObserved, 'maxRetriesPerRequest', 'max retries per request');
|
|
163
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
|
+
}
|
|
164
177
|
const ppeDiffs: Diffs = {
|
|
165
178
|
pass: true,
|
|
166
179
|
actual: ['PPE Events:'],
|
|
@@ -177,12 +190,7 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
|
|
|
177
190
|
|
|
178
191
|
for (const ppeEvent of uniquePpeEvents) {
|
|
179
192
|
if (!(ppeEvent in expected)) {
|
|
180
|
-
isWithinInterval(
|
|
181
|
-
ppeDiffs,
|
|
182
|
-
actual[ppeEvent],
|
|
183
|
-
{ [ppeEvent]: 0 },
|
|
184
|
-
ppeEvent,
|
|
185
|
-
);
|
|
193
|
+
isWithinInterval(ppeDiffs, actual[ppeEvent], { [ppeEvent]: 0 }, ppeEvent);
|
|
186
194
|
continue;
|
|
187
195
|
}
|
|
188
196
|
if (!(ppeEvent in actual)) {
|
|
@@ -198,7 +206,11 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
|
|
|
198
206
|
diffs.pass = ppeDiffs.pass;
|
|
199
207
|
diffs.actual.push(ppeDiffs.actual.join('\n '));
|
|
200
208
|
diffs.expected.push(ppeDiffs.expected.join('\n '));
|
|
201
|
-
failedAssertions.push(
|
|
209
|
+
failedAssertions.push(
|
|
210
|
+
`Failed PPE event counts check, expected ${JSON.stringify(
|
|
211
|
+
options.chargedEventCounts
|
|
212
|
+
)}, got ${JSON.stringify(chargedEventCounts)}.`
|
|
213
|
+
);
|
|
202
214
|
}
|
|
203
215
|
}
|
|
204
216
|
|
|
@@ -215,13 +227,15 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
|
|
|
215
227
|
diffs.pass = false;
|
|
216
228
|
diffs.actual.push(` logs=[${occuredLogs.join(', ')}]`);
|
|
217
229
|
diffs.expected.push(` logs=[]`);
|
|
218
|
-
failedAssertions.push(
|
|
230
|
+
failedAssertions.push(
|
|
231
|
+
`Failed forbidden logs check, expected [] but got [${occuredLogs.join(', ')}].`
|
|
232
|
+
);
|
|
219
233
|
}
|
|
220
234
|
}
|
|
221
235
|
|
|
222
236
|
return {
|
|
223
237
|
pass: diffs.pass,
|
|
224
|
-
message: () => `Run did not finish as expected. Failed assertions: ${failedAssertions.join(' '
|
|
238
|
+
message: () => `Run did not finish as expected. Failed assertions: ${failedAssertions.join(' ')}`,
|
|
225
239
|
actual: diffs.actual.join('\n '),
|
|
226
240
|
expected: diffs.expected.join('\n '),
|
|
227
241
|
};
|
|
@@ -257,17 +271,16 @@ export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
|
|
|
257
271
|
};
|
|
258
272
|
|
|
259
273
|
type Diffs = {
|
|
260
|
-
pass: boolean
|
|
261
|
-
actual: string[]
|
|
262
|
-
expected: string[]
|
|
263
|
-
|
|
264
|
-
}
|
|
274
|
+
pass: boolean;
|
|
275
|
+
actual: string[];
|
|
276
|
+
expected: string[];
|
|
277
|
+
};
|
|
265
278
|
|
|
266
279
|
const isWithinInterval = <T extends string>(
|
|
267
280
|
diffs: Diffs,
|
|
268
281
|
actual: number | undefined,
|
|
269
282
|
options: Record<T, Interval | null>,
|
|
270
|
-
intervalOption: T
|
|
283
|
+
intervalOption: T
|
|
271
284
|
) => {
|
|
272
285
|
const expected = options[intervalOption];
|
|
273
286
|
if (expected === null) {
|
|
@@ -279,7 +292,7 @@ const isWithinInterval = <T extends string>(
|
|
|
279
292
|
diffs.pass = false;
|
|
280
293
|
diffs.actual.push(`${intervalOption}=${actual}`);
|
|
281
294
|
diffs.expected.push(`${intervalOption}=${expected}`);
|
|
282
|
-
return false
|
|
295
|
+
return false;
|
|
283
296
|
}
|
|
284
297
|
} else if (typeof expected === 'object') {
|
|
285
298
|
const { min, max } = expected;
|
|
@@ -287,8 +300,8 @@ const isWithinInterval = <T extends string>(
|
|
|
287
300
|
diffs.pass = false;
|
|
288
301
|
diffs.actual.push(`${intervalOption}=${actual}`);
|
|
289
302
|
diffs.expected.push(`${intervalOption}=<${min ?? ''},${max ?? ''}>`);
|
|
290
|
-
return false
|
|
303
|
+
return false;
|
|
291
304
|
}
|
|
292
305
|
}
|
|
293
|
-
return
|
|
306
|
+
return;
|
|
294
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
|
-
await annotate(runLink
|
|
114
|
+
await annotate(`${name} - ${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
|
|
211
|
-
|
|
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,34 +234,26 @@ 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
|
-
await annotate(runLink
|
|
256
|
+
await annotate(`${task.name} - ${runLink}`, 'run_link');
|
|
268
257
|
// @ts-expect-error: `TaskMeta` cannot be retyped
|
|
269
258
|
task.meta = {
|
|
270
259
|
runId: run.id,
|
|
@@ -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
|
+
};
|
package/lib/run-test-result.ts
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
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,58 +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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
maxRetriesPerRequest: number | null
|
|
50
|
-
}
|
|
46
|
+
status: RunStatus;
|
|
47
|
+
duration: Interval | null;
|
|
48
|
+
failedRequests: Interval | null;
|
|
49
|
+
requestsRetries: Interval | null;
|
|
50
|
+
forbiddenLogs: string[];
|
|
51
|
+
maxRetriesPerRequest: number | null;
|
|
52
|
+
};
|
|
51
53
|
export type IntervalOption<PpeEvent extends string> = keyof Pick<
|
|
52
54
|
ToFinishWithOptions<PpeEvent>,
|
|
53
|
-
| '
|
|
54
|
-
|
|
55
|
-
| 'failedRequests'
|
|
56
|
-
| 'duration'
|
|
57
|
-
>
|
|
55
|
+
'datasetItemCount' | 'requestsRetries' | 'failedRequests' | 'duration'
|
|
56
|
+
>;
|
|
58
57
|
|
|
59
58
|
export type ToFinishWithOptions<PpeEvent extends string> = Partial<ToFinishWithOptionsWithDefaults> & {
|
|
60
|
-
datasetItemCount: Interval
|
|
59
|
+
datasetItemCount: Interval;
|
|
61
60
|
/**
|
|
62
61
|
* Define expected charged (PPE) event counts. You can also define count as `Interval`.
|
|
63
62
|
*
|
|
@@ -72,26 +71,26 @@ export type ToFinishWithOptions<PpeEvent extends string> = Partial<ToFinishWithO
|
|
|
72
71
|
* If you omit any PPE event, it's expected count will be 0.
|
|
73
72
|
* Assertion will fail if `chargedEventCounts` doesn't contain some of the expected events.
|
|
74
73
|
*/
|
|
75
|
-
chargedEventCounts?: Record<PpeEvent, Interval
|
|
74
|
+
chargedEventCounts?: Record<PpeEvent, Interval>;
|
|
76
75
|
};
|
|
77
76
|
|
|
78
77
|
export type SdkCrawlerStatistics = {
|
|
79
|
-
requestsFinished: number
|
|
80
|
-
requestsFailed: number
|
|
81
|
-
requestsRetries: number
|
|
82
|
-
requestsFailedPerMinute: number
|
|
83
|
-
requestsFinishedPerMinute: number
|
|
84
|
-
requestMinDurationMillis: number
|
|
85
|
-
requestMaxDurationMillis: number
|
|
86
|
-
requestRetryHistogram: number[]
|
|
87
|
-
requestTotalFailedDurationMillis: number
|
|
88
|
-
requestTotalFinishedDurationMillis: number
|
|
89
|
-
crawlerStartedAt: string
|
|
90
|
-
crawlerFinishedAt: string
|
|
91
|
-
statsPersistedAt: string
|
|
92
|
-
crawlerRuntimeMillis: number
|
|
93
|
-
crawlerLastStartTimestamp: number
|
|
94
|
-
}
|
|
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
|
+
};
|
|
95
94
|
|
|
96
95
|
export type RunStatus =
|
|
97
96
|
| 'SUCCEEDED'
|
|
@@ -101,22 +100,22 @@ export type RunStatus =
|
|
|
101
100
|
| 'ABORTING'
|
|
102
101
|
| 'ABORTED'
|
|
103
102
|
| 'TIMING-OUT'
|
|
104
|
-
| 'TIMED-OUT'
|
|
103
|
+
| 'TIMED-OUT';
|
|
105
104
|
|
|
106
105
|
export interface ActorMatchers<R = unknown> {
|
|
107
|
-
toBeArray: () => R
|
|
108
|
-
toBeBoolean: () => R
|
|
109
|
-
toBeEmptyArray: () => R
|
|
110
|
-
toBeNonEmptyArray: () => R
|
|
111
|
-
toBeNonEmptyString: () => R
|
|
112
|
-
toBeNumber: () => R
|
|
113
|
-
toBeFalse: () => R
|
|
114
|
-
toBeTrue: () => R
|
|
115
|
-
toBeNonEmptyObject: () => R
|
|
116
|
-
toBeObject: () => R
|
|
117
|
-
toBeString: () => R
|
|
118
|
-
toBeWholeNumber: () => R
|
|
119
|
-
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;
|
|
120
119
|
/**
|
|
121
120
|
* Validates the following properties of a run:
|
|
122
121
|
* - `status` (default: `SUCCEEDED`)
|
|
@@ -128,9 +127,9 @@ export interface ActorMatchers<R = unknown> {
|
|
|
128
127
|
* - `datasetItemCount` (required)
|
|
129
128
|
* - `chargedEventCounts`
|
|
130
129
|
*/
|
|
131
|
-
toFinishWith: <PpeEvent extends string>(options: ToFinishWithOptions<PpeEvent>) => Promise<R
|
|
132
|
-
toStartWith: (prefix: string) => R
|
|
133
|
-
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;
|
|
134
133
|
}
|
|
135
134
|
|
|
136
135
|
export type ActorTestOptions = Omit<TestOptions, 'retry'> & {
|
|
@@ -146,6 +145,8 @@ export type ActorTestOptions = Omit<TestOptions, 'retry'> & {
|
|
|
146
145
|
|
|
147
146
|
declare module 'vitest' {
|
|
148
147
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
149
|
-
interface Assertion<T = any> extends ActorMatchers<T> {
|
|
150
|
-
|
|
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 {}
|
|
151
152
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apify-test-tools",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
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",
|