apify-test-tools 0.1.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/README.md +23 -0
- package/bin/build.ts +287 -0
- package/bin/consts.ts +6 -0
- package/bin/git.ts +56 -0
- package/bin/github.ts +75 -0
- package/bin/main.ts +145 -0
- package/bin/slack.ts +72 -0
- package/bin/test-report.ts +146 -0
- package/bin/types.ts +102 -0
- package/bin/utils.ts +204 -0
- package/dist/bin/build.d.ts +18 -0
- package/dist/bin/build.d.ts.map +1 -0
- package/dist/bin/build.js +222 -0
- package/dist/bin/build.js.map +1 -0
- package/dist/bin/consts.d.ts +4 -0
- package/dist/bin/consts.d.ts.map +1 -0
- package/dist/bin/consts.js +6 -0
- package/dist/bin/consts.js.map +1 -0
- package/dist/bin/git.d.ts +7 -0
- package/dist/bin/git.d.ts.map +1 -0
- package/dist/bin/git.js +47 -0
- package/dist/bin/git.js.map +1 -0
- package/dist/bin/github.d.ts +12 -0
- package/dist/bin/github.d.ts.map +1 -0
- package/dist/bin/github.js +64 -0
- package/dist/bin/github.js.map +1 -0
- package/dist/bin/main.d.ts +3 -0
- package/dist/bin/main.d.ts.map +1 -0
- package/dist/bin/main.js +117 -0
- package/dist/bin/main.js.map +1 -0
- package/dist/bin/slack.d.ts +12 -0
- package/dist/bin/slack.d.ts.map +1 -0
- package/dist/bin/slack.js +46 -0
- package/dist/bin/slack.js.map +1 -0
- package/dist/bin/test-report.d.ts +10 -0
- package/dist/bin/test-report.d.ts.map +1 -0
- package/dist/bin/test-report.js +74 -0
- package/dist/bin/test-report.js.map +1 -0
- package/dist/bin/types.d.ts +88 -0
- package/dist/bin/types.d.ts.map +1 -0
- package/dist/bin/types.js +2 -0
- package/dist/bin/types.js.map +1 -0
- package/dist/bin/utils.d.ts +26 -0
- package/dist/bin/utils.d.ts.map +1 -0
- package/dist/bin/utils.js +164 -0
- package/dist/bin/utils.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/consts.d.ts +3 -0
- package/dist/lib/consts.d.ts.map +1 -0
- package/dist/lib/consts.js +14 -0
- package/dist/lib/consts.js.map +1 -0
- package/dist/lib/extend-expect.d.ts +3 -0
- package/dist/lib/extend-expect.d.ts.map +1 -0
- package/dist/lib/extend-expect.js +228 -0
- package/dist/lib/extend-expect.js.map +1 -0
- package/dist/lib/lib.d.ts +16 -0
- package/dist/lib/lib.d.ts.map +1 -0
- package/dist/lib/lib.js +91 -0
- package/dist/lib/lib.js.map +1 -0
- package/dist/lib/run-test-result.d.ts +20 -0
- package/dist/lib/run-test-result.d.ts.map +1 -0
- package/dist/lib/run-test-result.js +62 -0
- package/dist/lib/run-test-result.js.map +1 -0
- package/dist/lib/types.d.ts +107 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/utils.d.ts +7 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +30 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/test/unit/custom-matchers.test.d.ts +2 -0
- package/dist/test/unit/custom-matchers.test.d.ts.map +1 -0
- package/dist/test/unit/custom-matchers.test.js +53 -0
- package/dist/test/unit/custom-matchers.test.js.map +1 -0
- package/dist/test/unit/should-built-and-test.test.d.ts +2 -0
- package/dist/test/unit/should-built-and-test.test.d.ts.map +1 -0
- package/dist/test/unit/should-built-and-test.test.js +136 -0
- package/dist/test/unit/should-built-and-test.test.js.map +1 -0
- package/dist/test/unit/test-actor.test.d.ts +2 -0
- package/dist/test/unit/test-actor.test.d.ts.map +1 -0
- package/dist/test/unit/test-actor.test.js +17 -0
- package/dist/test/unit/test-actor.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/example-github-events/pull-request.json +523 -0
- package/example-github-events/push.json +216 -0
- package/index.ts +2 -0
- package/lib/consts.ts +15 -0
- package/lib/extend-expect.ts +268 -0
- package/lib/lib.ts +134 -0
- package/lib/run-test-result.ts +70 -0
- package/lib/types.ts +131 -0
- package/lib/utils.ts +49 -0
- package/package.json +41 -0
- package/scripts/hook-job-started.sh +11 -0
- package/test/tsconfig.json +15 -0
- package/test/unit/custom-matchers.test.ts +61 -0
- package/test/unit/should-built-and-test.test.ts +166 -0
- package/test/unit/test-actor.test.ts +17 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ActorRun, ApifyClient } from 'apify-client';
|
|
2
|
+
import type { Dataset, SdkCrawlerStatistics } from './types';
|
|
3
|
+
|
|
4
|
+
export class RunTestResult {
|
|
5
|
+
private log: string | undefined;
|
|
6
|
+
private dataset: Dataset<unknown> | undefined;
|
|
7
|
+
private statistics: SdkCrawlerStatistics | undefined;
|
|
8
|
+
private runInfo: ActorRun | undefined;
|
|
9
|
+
private input: unknown | undefined;
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
private readonly apifyClient: ApifyClient,
|
|
13
|
+
private readonly run: ActorRun,
|
|
14
|
+
) { /**/ }
|
|
15
|
+
|
|
16
|
+
getStatistics = async (): Promise<SdkCrawlerStatistics | undefined> => {
|
|
17
|
+
if (this.statistics) {
|
|
18
|
+
return this.statistics;
|
|
19
|
+
}
|
|
20
|
+
const kvs = this.apifyClient.keyValueStore(this.run.defaultKeyValueStoreId);
|
|
21
|
+
const stats = await kvs.getRecord('SDK_CRAWLER_STATISTICS_0');
|
|
22
|
+
this.statistics = stats?.value as unknown as SdkCrawlerStatistics;
|
|
23
|
+
return this.statistics;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
getLog = async (): Promise<string> => {
|
|
27
|
+
if (this.log) {
|
|
28
|
+
return this.log;
|
|
29
|
+
}
|
|
30
|
+
const runLog = await this.apifyClient.run(this.id).log().get();
|
|
31
|
+
this.log = runLog;
|
|
32
|
+
return runLog as string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
getDataset = async<T>(): Promise<Dataset<T>> => {
|
|
36
|
+
if (this.dataset) {
|
|
37
|
+
return this.dataset as Dataset<T>;
|
|
38
|
+
}
|
|
39
|
+
const dataset = this.apifyClient.dataset(this.run.defaultDatasetId);
|
|
40
|
+
const items = (await dataset.listItems()).items as T[];
|
|
41
|
+
this.dataset = { items };
|
|
42
|
+
return this.dataset as Dataset<T>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
getInput = async<T>(): Promise<T> => {
|
|
46
|
+
if (this.input) {
|
|
47
|
+
return this.input as T
|
|
48
|
+
}
|
|
49
|
+
const kvs = this.apifyClient.keyValueStore(this.run.defaultKeyValueStoreId);
|
|
50
|
+
const input = await kvs.getRecord('INPUT');
|
|
51
|
+
return input as T;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getRunInfo = async (): Promise<ActorRun> => {
|
|
55
|
+
if (this.runInfo) {
|
|
56
|
+
return this.runInfo;
|
|
57
|
+
}
|
|
58
|
+
const run = this.apifyClient.run(this.run.id);
|
|
59
|
+
this.runInfo = await run.get();
|
|
60
|
+
return this.runInfo!;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
get id(): string {
|
|
64
|
+
return this.run.id;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get status(): string {
|
|
68
|
+
return this.run.status;
|
|
69
|
+
}
|
|
70
|
+
}
|
package/lib/types.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { ActorCallOptions } from 'apify-client';
|
|
2
|
+
import { Assertion } from 'vitest';
|
|
3
|
+
|
|
4
|
+
export type ActorBuild = {
|
|
5
|
+
buildId: string;
|
|
6
|
+
actorId: string;
|
|
7
|
+
buildNumber: string;
|
|
8
|
+
actorName: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type RunOptions<T> = {
|
|
12
|
+
input: Omit<T, 'actorName'>
|
|
13
|
+
options?: ActorCallOptions
|
|
14
|
+
prefilledInput?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type Dataset<T> = {
|
|
18
|
+
items: T[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type OpenInterval = {
|
|
22
|
+
min: number
|
|
23
|
+
max?: undefined
|
|
24
|
+
} | {
|
|
25
|
+
min?: undefined
|
|
26
|
+
max: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type ClosedInterval = {
|
|
30
|
+
min: number
|
|
31
|
+
max: number
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type Interval = number | OpenInterval | ClosedInterval
|
|
35
|
+
|
|
36
|
+
export type ToFinishWithOptionsWithDefaults = {
|
|
37
|
+
status: RunStatus
|
|
38
|
+
duration: Interval | null
|
|
39
|
+
failedRequests: Interval | null
|
|
40
|
+
requestsRetries: Interval | null
|
|
41
|
+
forbiddenLogs: string[]
|
|
42
|
+
}
|
|
43
|
+
export type IntervalOption<PpeEvent extends string> = keyof Pick<
|
|
44
|
+
ToFinishWithOptions<PpeEvent>,
|
|
45
|
+
| 'datasetItemCount'
|
|
46
|
+
| 'requestsRetries'
|
|
47
|
+
| 'failedRequests'
|
|
48
|
+
| 'duration'
|
|
49
|
+
>
|
|
50
|
+
|
|
51
|
+
export type ToFinishWithOptions<PpeEvent extends string> = Partial<ToFinishWithOptionsWithDefaults> & {
|
|
52
|
+
datasetItemCount: Interval
|
|
53
|
+
/**
|
|
54
|
+
* Define expected charged (PPE) event counts. You can also define count as `Interval`.
|
|
55
|
+
*
|
|
56
|
+
* Example
|
|
57
|
+
* ```ts
|
|
58
|
+
* chargedEventCounts: {
|
|
59
|
+
* 'actor-start': 4,
|
|
60
|
+
* 'item-pushed': { min: 10, max: 20 },
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* If you omit any PPE event, it's expected count will be 0.
|
|
65
|
+
* Assertion will fail if `chargedEventCounts` doesn't contain some of the expected events.
|
|
66
|
+
*/
|
|
67
|
+
chargedEventCounts?: Record<PpeEvent, Interval>
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type SdkCrawlerStatistics = {
|
|
71
|
+
requestsFinished: number
|
|
72
|
+
requestsFailed: number
|
|
73
|
+
requestsRetries: number
|
|
74
|
+
requestsFailedPerMinute: number
|
|
75
|
+
requestsFinishedPerMinute: number
|
|
76
|
+
requestMinDurationMillis: number
|
|
77
|
+
requestMaxDurationMillis: number
|
|
78
|
+
requestRetryHistogram: number[]
|
|
79
|
+
requestTotalFailedDurationMillis: number
|
|
80
|
+
requestTotalFinishedDurationMillis: number
|
|
81
|
+
crawlerStartedAt: string
|
|
82
|
+
crawlerFinishedAt: string
|
|
83
|
+
statsPersistedAt: string
|
|
84
|
+
crawlerRuntimeMillis: number
|
|
85
|
+
crawlerLastStartTimestamp: number
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type RunStatus =
|
|
89
|
+
| 'SUCCEEDED'
|
|
90
|
+
| 'READY'
|
|
91
|
+
| 'RUNNING'
|
|
92
|
+
| 'FAILED'
|
|
93
|
+
| 'ABORTING'
|
|
94
|
+
| 'ABORTED'
|
|
95
|
+
| 'TIMING-OUT'
|
|
96
|
+
| 'TIMED-OUT'
|
|
97
|
+
|
|
98
|
+
export interface ActorMatchers<R = unknown> {
|
|
99
|
+
toBeArray: () => R
|
|
100
|
+
toBeBoolean: () => R
|
|
101
|
+
toBeEmptyArray: () => R
|
|
102
|
+
toBeNonEmptyArray: () => R
|
|
103
|
+
toBeNonEmptyString: () => R
|
|
104
|
+
toBeNumber: () => R
|
|
105
|
+
toBeFalse: () => R
|
|
106
|
+
toBeTrue: () => R
|
|
107
|
+
toBeNonEmptyObject: () => R
|
|
108
|
+
toBeObject: () => R
|
|
109
|
+
toBeString: () => R
|
|
110
|
+
toBeWholeNumber: () => R
|
|
111
|
+
toBeWithinRange: (lower: number, upper: number) => R
|
|
112
|
+
/**
|
|
113
|
+
* Validates the following properties of a run:
|
|
114
|
+
* - `status` (default: `SUCCEEDED`)
|
|
115
|
+
* - `duration` in milliseconds (default: `{ min: 600, max: 600_000}` - <0.6s, 10min>)
|
|
116
|
+
* - `failedRequests` (default: `0`)
|
|
117
|
+
* - `requestsRetries` (default: `{ max: 3 }`)
|
|
118
|
+
* - `forbiddenLogs` (default: `['ReferenceError', 'TypeError']`)
|
|
119
|
+
* - `datasetItemCount` (required)
|
|
120
|
+
* - `chargedEventCounts`
|
|
121
|
+
*/
|
|
122
|
+
toFinishWith: <PpeEvent extends string>(options: ToFinishWithOptions<PpeEvent>) => Promise<R>
|
|
123
|
+
toStartWith: (prefix: string) => R
|
|
124
|
+
hard: <T>(actual: T, message?: string) => Assertion
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
declare module 'vitest' {
|
|
128
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
129
|
+
interface Assertion<T = any> extends ActorMatchers<T> { }
|
|
130
|
+
interface AsymmetricMatchersContaining extends ActorMatchers { }
|
|
131
|
+
}
|
package/lib/utils.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ApifyClient } from 'apify-client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Gets prefilled values for a provided build or, if not provided, uses actor's
|
|
5
|
+
* default build (this is usually `latest`).
|
|
6
|
+
*/
|
|
7
|
+
export const getActorPrefilledInput = async (
|
|
8
|
+
apifyClient: ApifyClient,
|
|
9
|
+
actorNameOrId: string,
|
|
10
|
+
buildId: string | undefined,
|
|
11
|
+
) => {
|
|
12
|
+
if (!buildId) {
|
|
13
|
+
const actorInfo = await apifyClient.actor(actorNameOrId).get();
|
|
14
|
+
|
|
15
|
+
const defaultBuildTag = actorInfo?.defaultRunOptions.build;
|
|
16
|
+
|
|
17
|
+
const taggedBuild = actorInfo?.taggedBuilds?.[defaultBuildTag || ''];
|
|
18
|
+
buildId = taggedBuild?.buildId;
|
|
19
|
+
|
|
20
|
+
if (!buildId) {
|
|
21
|
+
console.error(`Coudn't find default build for actor ${actorNameOrId}. Prefilled values will not be used.`);
|
|
22
|
+
return {}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const buildInfo = await apifyClient.build(buildId).get();
|
|
27
|
+
|
|
28
|
+
const inputSchema = buildInfo?.actorDefinition?.input as {
|
|
29
|
+
properties: Record<string, { prefill?: unknown }>
|
|
30
|
+
} | undefined
|
|
31
|
+
|
|
32
|
+
if (!inputSchema) {
|
|
33
|
+
console.error(
|
|
34
|
+
`Coudn't find input schema definition for actor ${actorNameOrId}, build ${buildId}.`,
|
|
35
|
+
'Prefilled values will not be used',
|
|
36
|
+
);
|
|
37
|
+
return {}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const prefill: Record<string, unknown> = {};
|
|
41
|
+
|
|
42
|
+
for (const [propertyName, propertyValue] of Object.entries(inputSchema.properties)) {
|
|
43
|
+
if (propertyValue.prefill !== undefined) {
|
|
44
|
+
prefill[propertyName] = propertyValue.prefill;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return prefill;
|
|
49
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "apify-test-tools",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "TBD",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=18.0.0"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"apify-test-tools": "dist/bin/main.js"
|
|
11
|
+
},
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"main": "./dist/lib/lib.js",
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@apify/consts": "^2.43.0",
|
|
16
|
+
"@slack/web-api": "^7.9.2",
|
|
17
|
+
"apify-client": "^2.12.6",
|
|
18
|
+
"yargs": "^18.0.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@apify/eslint-config-ts": "^0.3.0",
|
|
22
|
+
"@apify/tsconfig": "^0.1.0",
|
|
23
|
+
"@types/yargs": "^17.0.33",
|
|
24
|
+
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
|
25
|
+
"@typescript-eslint/parser": "^6.7.2",
|
|
26
|
+
"eslint": "^8.50.0",
|
|
27
|
+
"tsx": "^4.19.2",
|
|
28
|
+
"typescript": "^5.7",
|
|
29
|
+
"vitest": "^3.2.4"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"start": "npm run dist/bin/main.js",
|
|
33
|
+
"start:dev": "GITHUB_WORKSPACE=local-clone tsx bin/main.ts",
|
|
34
|
+
"build": "tsc && chmod +x ./dist/bin/main.js",
|
|
35
|
+
"lint": "eslint ./src --ext .ts",
|
|
36
|
+
"lint:fix": "eslint ./src --ext .ts --fix",
|
|
37
|
+
"test": "vitest run"
|
|
38
|
+
},
|
|
39
|
+
"author": "oklinov",
|
|
40
|
+
"license": "ISC"
|
|
41
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
reponame=$(jq '.repository.name' ${GITHUB_EVENT_PATH})
|
|
2
|
+
commit_message=$(jq '.head_commit.message' ${GITHUB_EVENT_PATH})
|
|
3
|
+
pr_title=$(jq '.pull_request.title' ${GITHUB_EVENT_PATH})
|
|
4
|
+
|
|
5
|
+
if [ "${commit_message}" != "null" ]; then
|
|
6
|
+
echo "Received push event from repo ${reponame} with commit message ${commit_message}"
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
if [ "${pr_title}" != "null" ]; then
|
|
10
|
+
echo "Received PR event from repo ${reponame} with PR title ${pr_title}"
|
|
11
|
+
fi
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@apify/tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"target": "ES2022",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"noUnusedLocals": false,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"lib": ["DOM", "es2021"],
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
},
|
|
12
|
+
"include": [
|
|
13
|
+
"./**/*",
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import process from 'process';
|
|
2
|
+
import { describe } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { ApifyClient } from 'apify-client';
|
|
5
|
+
import { testTestActor } from '../../lib/lib';
|
|
6
|
+
import { RunTestResult } from '../../lib/run-test-result';
|
|
7
|
+
|
|
8
|
+
type PpeEventType =
|
|
9
|
+
| 'actor-start'
|
|
10
|
+
| 'search-page-scraped'
|
|
11
|
+
| 'ads-scraped'
|
|
12
|
+
enum PpeEventEnum {
|
|
13
|
+
ACTOR_START = 'actor-start',
|
|
14
|
+
SEARCH_PAGE_SCRAPED = 'search-page-scraped',
|
|
15
|
+
ADS_SCRAPED = 'ads-scraped',
|
|
16
|
+
}
|
|
17
|
+
const PPE_EVENT_CONST = {
|
|
18
|
+
ACTOR_START: 'actor-start',
|
|
19
|
+
ITEM_PUSHED: 'search-page-scraped',
|
|
20
|
+
ADS_SCRAPED: 'ads-scraped',
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
describe('custom-matchers', { timeout: 100_000 }, () => {
|
|
24
|
+
testTestActor('basic', async ({ expect }) => {
|
|
25
|
+
const apifyClient = new ApifyClient({ token: process.env.TESTER_APIFY_TOKEN });
|
|
26
|
+
const run = await apifyClient.run('k5MNKmaDGHlABDn2I').get();
|
|
27
|
+
const runResult = new RunTestResult(apifyClient, run!);
|
|
28
|
+
await expect(runResult).toFinishWith<PpeEventType>({
|
|
29
|
+
datasetItemCount: { min: 10, max: 20 },
|
|
30
|
+
duration: 10,
|
|
31
|
+
status: 'TIMING-OUT',
|
|
32
|
+
chargedEventCounts: {
|
|
33
|
+
'actor-start': 1,
|
|
34
|
+
'search-page-scraped': { min: 1 },
|
|
35
|
+
'ads-scraped': 3,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
await expect(runResult).toFinishWith<PpeEventEnum>({
|
|
40
|
+
datasetItemCount: { min: 10, max: 20 },
|
|
41
|
+
chargedEventCounts: {
|
|
42
|
+
'actor-start': 1,
|
|
43
|
+
'search-page-scraped': { min: 1 },
|
|
44
|
+
'ads-scraped': 0,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await expect(runResult).toFinishWith<typeof PPE_EVENT_CONST[keyof typeof PPE_EVENT_CONST]>({
|
|
49
|
+
datasetItemCount: { min: 1, max: 20 },
|
|
50
|
+
chargedEventCounts: {
|
|
51
|
+
'actor-start': 1,
|
|
52
|
+
'search-page-scraped': { min: 1 },
|
|
53
|
+
'ads-scraped': 0,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(3).toEqual(3);
|
|
58
|
+
expect.hard(5).toEqual(5);
|
|
59
|
+
expect(10).toEqual(10);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import type { ActorConfig } from '../../bin/types.js';
|
|
4
|
+
import { getChangedActors } from '../../bin/utils.js';
|
|
5
|
+
|
|
6
|
+
describe('Should build and test parser', () => {
|
|
7
|
+
// From https://github.com/apify-store/testing-repo-for-github-actions
|
|
8
|
+
const ACTOR_CONFIGS: ActorConfig[] = [
|
|
9
|
+
{
|
|
10
|
+
actorName: 'lukaskrivka/testing-github-integration-1',
|
|
11
|
+
folder: 'actors/lukaskrivka_testing-github-integration-1',
|
|
12
|
+
isStandalone: false,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
actorName: 'lukaskrivka/testing-github-integration-2',
|
|
16
|
+
folder: 'actors/lukaskrivka_testing-github-integration-2',
|
|
17
|
+
isStandalone: false,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
actorName: 'lukaskrivka/test-standalone',
|
|
21
|
+
folder: 'standalone-actors/lukaskrivka_test-standalone',
|
|
22
|
+
isStandalone: true,
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
test('Ignores dev-only readme', async () => {
|
|
27
|
+
const FILES = ['README.md', 'code/README.md', 'shared/README.md'];
|
|
28
|
+
|
|
29
|
+
const { actorsChanged, codeChanged } = await getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: false, filepathsChanged: FILES });
|
|
30
|
+
|
|
31
|
+
expect(actorsChanged).toEqual([]);
|
|
32
|
+
expect(codeChanged).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('Ignores other ignored files and folders', async () => {
|
|
36
|
+
const FILES = ['.vscode/', '.gitignore', '.husky/', '.eslintrc', '.editorconfig', '.actor/'];
|
|
37
|
+
|
|
38
|
+
const { actorsChanged, codeChanged } = await getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: false, filepathsChanged: FILES });
|
|
39
|
+
|
|
40
|
+
expect(actorsChanged).toEqual([]);
|
|
41
|
+
expect(codeChanged).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('Only builds latest for all Actors', async () => {
|
|
45
|
+
const FILES = ['shared/CHANGELOG.md', 'CHANGELOG.md'];
|
|
46
|
+
|
|
47
|
+
const { actorsChanged, codeChanged } = await getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: true, filepathsChanged: FILES });
|
|
48
|
+
|
|
49
|
+
expect(actorsChanged).toEqual(ACTOR_CONFIGS.filter(({ isStandalone }) => !isStandalone));
|
|
50
|
+
expect(codeChanged).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('Code updated, tests miniactors', async () => {
|
|
54
|
+
const FILES = ['code/src/main.ts', 'package.json'];
|
|
55
|
+
|
|
56
|
+
const { actorsChanged, codeChanged } = await getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: false, filepathsChanged: FILES });
|
|
57
|
+
|
|
58
|
+
expect(actorsChanged).toEqual(ACTOR_CONFIGS.filter(({ isStandalone }) => !isStandalone));
|
|
59
|
+
expect(codeChanged).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('Specific Actor functionality configs updated', async () => {
|
|
63
|
+
const FILES = ['actors/lukaskrivka_testing-github-integration-1/.actor/actor.json', 'standalone-actors/lukaskrivka_test-standalone/Dockerfile'];
|
|
64
|
+
|
|
65
|
+
const { actorsChanged, codeChanged } = await getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: false, filepathsChanged: FILES });
|
|
66
|
+
|
|
67
|
+
expect(actorsChanged).toEqual([ACTOR_CONFIGS[0], ACTOR_CONFIGS[2]]);
|
|
68
|
+
expect(codeChanged).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('src/main.ts updated', async () => {
|
|
72
|
+
const FILES = [
|
|
73
|
+
'src/main.ts',
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
const { actorsChanged, codeChanged } = await getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: true, filepathsChanged: FILES });
|
|
77
|
+
|
|
78
|
+
expect(actorsChanged).toEqual(ACTOR_CONFIGS.slice(0, 2));
|
|
79
|
+
expect(codeChanged).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('Miniactor, Code and standalone actor updated,', async () => {
|
|
83
|
+
const FILES = [
|
|
84
|
+
'actors/lukaskrivka_testing-github-integration-1/.actor/actor.json',
|
|
85
|
+
'code/src/main.ts',
|
|
86
|
+
'standalone-actors/lukaskrivka_test-standalone/Dockerfile',
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
const { actorsChanged, codeChanged } = await getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: false, filepathsChanged: FILES });
|
|
90
|
+
|
|
91
|
+
expect(actorsChanged).toEqual(ACTOR_CONFIGS);
|
|
92
|
+
expect(codeChanged).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('Specific Actor non-functional configs updated', async () => {
|
|
96
|
+
const FILES = ['actors/lukaskrivka_testing-github-integration-2/.actor/README.md', 'standalone-actors/lukaskrivka_test-standalone/CHANGELOG.md'];
|
|
97
|
+
|
|
98
|
+
const { actorsChanged, codeChanged } = await getChangedActors({ actorConfigs: ACTOR_CONFIGS, isLatest: false, filepathsChanged: FILES });
|
|
99
|
+
|
|
100
|
+
expect(actorsChanged).toEqual([]);
|
|
101
|
+
expect(codeChanged).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('Google Maps real user-case that had undefined', async () => {
|
|
105
|
+
const FILES = ['actors/compass_Google-Maps-Reviews-Scraper/.actor/INPUT_SCHEMA.json', 'actors/compass_crawler-google-places/.actor/INPUT_SCHEMA.json',
|
|
106
|
+
'code/src/consts.ts', 'code/src/crawlers/cheerio/routes.ts', 'code/src/detail_page_handle.ts', 'code/src/enqueue_places.ts',
|
|
107
|
+
'code/src/helper-classes/initialize-all.ts', 'code/src/helper-classes/stats.ts', 'code/src/helper-classes/unmatched-categories.ts',
|
|
108
|
+
'code/src/main.ts', 'code/src/typedefs/general.ts', 'code/src/utils/background-enqueue.ts'];
|
|
109
|
+
|
|
110
|
+
const ACTOR_CONFIGS_GOOGLE_MAPS: ActorConfig[] = [
|
|
111
|
+
{
|
|
112
|
+
// Edge case of capitals in actor name :)
|
|
113
|
+
actorName: 'compass/Google-Maps-Reviews-Scraper',
|
|
114
|
+
folder: 'actors/compass_Google-Maps-Reviews-Scraper',
|
|
115
|
+
isStandalone: false,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
actorName: 'compass/crawler-google-places',
|
|
119
|
+
folder: 'actors/compass_crawler-google-places',
|
|
120
|
+
isStandalone: false,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
actorName: 'compass/easy-google-maps',
|
|
124
|
+
folder: 'actors/compass_easy-google-maps',
|
|
125
|
+
isStandalone: false,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
actorName: 'compass/google-maps-extractor',
|
|
129
|
+
folder: 'actors/compass_google-maps-extractor',
|
|
130
|
+
isStandalone: false,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
actorName: 'compass/google-places-api',
|
|
134
|
+
folder: 'actors/compass_google-places-api',
|
|
135
|
+
isStandalone: false,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
actorName: 'lukaskrivka/google-maps-with-contact-details',
|
|
139
|
+
folder: 'actors/lukaskrivka_google-maps-with-contact-details',
|
|
140
|
+
isStandalone: false,
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
actorName: 'natasha.lekh/gas-prices-scraper',
|
|
144
|
+
folder: 'actors/natasha.lekh_gas-prices-scraper',
|
|
145
|
+
isStandalone: false,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
actorName: 'natasha.lekh/vegan-places-finder',
|
|
149
|
+
folder: 'actors/natasha.lekh_vegan-places-finder',
|
|
150
|
+
isStandalone: false,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
actorName: 'lukaskrivka/google-maps-scraper-orchestrator',
|
|
154
|
+
folder: 'standalone-actors/lukaskrivka_google-maps-scraper-orchestrator',
|
|
155
|
+
isStandalone: true,
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
const { actorsChanged, codeChanged } = await getChangedActors({
|
|
160
|
+
actorConfigs: ACTOR_CONFIGS_GOOGLE_MAPS, isLatest: false, filepathsChanged: FILES,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
expect(actorsChanged).toEqual(ACTOR_CONFIGS_GOOGLE_MAPS.filter(({ isStandalone }) => !isStandalone));
|
|
164
|
+
expect(codeChanged).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, testActor } from '../../lib/lib';
|
|
2
|
+
|
|
3
|
+
describe('testActor tests', async () => {
|
|
4
|
+
testActor('compass/google-maps-extractor', 'basic', async ({ expect, run }) => {
|
|
5
|
+
const runResult = await run({
|
|
6
|
+
input: {},
|
|
7
|
+
prefilledInput: true,
|
|
8
|
+
options: {
|
|
9
|
+
memory: 512,
|
|
10
|
+
},
|
|
11
|
+
})
|
|
12
|
+
const input = await runResult.getInput<{ maxCrawledPlacesPerSearch: number }>();
|
|
13
|
+
await expect(runResult).toFinishWith({
|
|
14
|
+
datasetItemCount: input.maxCrawledPlacesPerSearch,
|
|
15
|
+
})
|
|
16
|
+
});
|
|
17
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@apify/tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"declaration": true,
|
|
5
|
+
"module": "ES2022",
|
|
6
|
+
"target": "ES2022",
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"noUnusedLocals": false,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"lib": ["DOM", "es2021"],
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
},
|
|
14
|
+
"include": [
|
|
15
|
+
"./index.ts",
|
|
16
|
+
"./bin/**/*",
|
|
17
|
+
"./lib/**/*",
|
|
18
|
+
"./test/**/*",
|
|
19
|
+
]
|
|
20
|
+
}
|