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,216 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ref": "refs/heads/master",
|
|
3
|
+
"before": "e15bf2a167bec5681afef2a5c057b8a10b78850d",
|
|
4
|
+
"after": "7f9e105e4ce7881f9def76db60bd8e4e58ead9f2",
|
|
5
|
+
"repository": {
|
|
6
|
+
"id": 530628540,
|
|
7
|
+
"node_id": "R_kgDOH6C_vA",
|
|
8
|
+
"name": "store-booking",
|
|
9
|
+
"full_name": "apify-projects/store-booking",
|
|
10
|
+
"private": true,
|
|
11
|
+
"owner": {
|
|
12
|
+
"name": "apify-projects",
|
|
13
|
+
"email": "hello@apify.com",
|
|
14
|
+
"login": "apify-projects",
|
|
15
|
+
"id": 69005150,
|
|
16
|
+
"node_id": "MDEyOk9yZ2FuaXphdGlvbjY5MDA1MTUw",
|
|
17
|
+
"avatar_url": "https://avatars.githubusercontent.com/u/69005150?v=4",
|
|
18
|
+
"gravatar_id": "",
|
|
19
|
+
"url": "https://api.github.com/users/apify-projects",
|
|
20
|
+
"html_url": "https://github.com/apify-projects",
|
|
21
|
+
"followers_url": "https://api.github.com/users/apify-projects/followers",
|
|
22
|
+
"following_url": "https://api.github.com/users/apify-projects/following{/other_user}",
|
|
23
|
+
"gists_url": "https://api.github.com/users/apify-projects/gists{/gist_id}",
|
|
24
|
+
"starred_url": "https://api.github.com/users/apify-projects/starred{/owner}{/repo}",
|
|
25
|
+
"subscriptions_url": "https://api.github.com/users/apify-projects/subscriptions",
|
|
26
|
+
"organizations_url": "https://api.github.com/users/apify-projects/orgs",
|
|
27
|
+
"repos_url": "https://api.github.com/users/apify-projects/repos",
|
|
28
|
+
"events_url": "https://api.github.com/users/apify-projects/events{/privacy}",
|
|
29
|
+
"received_events_url": "https://api.github.com/users/apify-projects/received_events",
|
|
30
|
+
"type": "Organization",
|
|
31
|
+
"site_admin": false
|
|
32
|
+
},
|
|
33
|
+
"html_url": "https://github.com/apify-projects/store-booking",
|
|
34
|
+
"description": null,
|
|
35
|
+
"fork": false,
|
|
36
|
+
"url": "https://github.com/apify-projects/store-booking",
|
|
37
|
+
"forks_url": "https://api.github.com/repos/apify-projects/store-booking/forks",
|
|
38
|
+
"keys_url": "https://api.github.com/repos/apify-projects/store-booking/keys{/key_id}",
|
|
39
|
+
"collaborators_url": "https://api.github.com/repos/apify-projects/store-booking/collaborators{/collaborator}",
|
|
40
|
+
"teams_url": "https://api.github.com/repos/apify-projects/store-booking/teams",
|
|
41
|
+
"hooks_url": "https://api.github.com/repos/apify-projects/store-booking/hooks",
|
|
42
|
+
"issue_events_url": "https://api.github.com/repos/apify-projects/store-booking/issues/events{/number}",
|
|
43
|
+
"events_url": "https://api.github.com/repos/apify-projects/store-booking/events",
|
|
44
|
+
"assignees_url": "https://api.github.com/repos/apify-projects/store-booking/assignees{/user}",
|
|
45
|
+
"branches_url": "https://api.github.com/repos/apify-projects/store-booking/branches{/branch}",
|
|
46
|
+
"tags_url": "https://api.github.com/repos/apify-projects/store-booking/tags",
|
|
47
|
+
"blobs_url": "https://api.github.com/repos/apify-projects/store-booking/git/blobs{/sha}",
|
|
48
|
+
"git_tags_url": "https://api.github.com/repos/apify-projects/store-booking/git/tags{/sha}",
|
|
49
|
+
"git_refs_url": "https://api.github.com/repos/apify-projects/store-booking/git/refs{/sha}",
|
|
50
|
+
"trees_url": "https://api.github.com/repos/apify-projects/store-booking/git/trees{/sha}",
|
|
51
|
+
"statuses_url": "https://api.github.com/repos/apify-projects/store-booking/statuses/{sha}",
|
|
52
|
+
"languages_url": "https://api.github.com/repos/apify-projects/store-booking/languages",
|
|
53
|
+
"stargazers_url": "https://api.github.com/repos/apify-projects/store-booking/stargazers",
|
|
54
|
+
"contributors_url": "https://api.github.com/repos/apify-projects/store-booking/contributors",
|
|
55
|
+
"subscribers_url": "https://api.github.com/repos/apify-projects/store-booking/subscribers",
|
|
56
|
+
"subscription_url": "https://api.github.com/repos/apify-projects/store-booking/subscription",
|
|
57
|
+
"commits_url": "https://api.github.com/repos/apify-projects/store-booking/commits{/sha}",
|
|
58
|
+
"git_commits_url": "https://api.github.com/repos/apify-projects/store-booking/git/commits{/sha}",
|
|
59
|
+
"comments_url": "https://api.github.com/repos/apify-projects/store-booking/comments{/number}",
|
|
60
|
+
"issue_comment_url": "https://api.github.com/repos/apify-projects/store-booking/issues/comments{/number}",
|
|
61
|
+
"contents_url": "https://api.github.com/repos/apify-projects/store-booking/contents/{+path}",
|
|
62
|
+
"compare_url": "https://api.github.com/repos/apify-projects/store-booking/compare/{base}...{head}",
|
|
63
|
+
"merges_url": "https://api.github.com/repos/apify-projects/store-booking/merges",
|
|
64
|
+
"archive_url": "https://api.github.com/repos/apify-projects/store-booking/{archive_format}{/ref}",
|
|
65
|
+
"downloads_url": "https://api.github.com/repos/apify-projects/store-booking/downloads",
|
|
66
|
+
"issues_url": "https://api.github.com/repos/apify-projects/store-booking/issues{/number}",
|
|
67
|
+
"pulls_url": "https://api.github.com/repos/apify-projects/store-booking/pulls{/number}",
|
|
68
|
+
"milestones_url": "https://api.github.com/repos/apify-projects/store-booking/milestones{/number}",
|
|
69
|
+
"notifications_url": "https://api.github.com/repos/apify-projects/store-booking/notifications{?since,all,participating}",
|
|
70
|
+
"labels_url": "https://api.github.com/repos/apify-projects/store-booking/labels{/name}",
|
|
71
|
+
"releases_url": "https://api.github.com/repos/apify-projects/store-booking/releases{/id}",
|
|
72
|
+
"deployments_url": "https://api.github.com/repos/apify-projects/store-booking/deployments",
|
|
73
|
+
"created_at": 1661859371,
|
|
74
|
+
"updated_at": "2024-07-01T22:15:13Z",
|
|
75
|
+
"pushed_at": 1721436715,
|
|
76
|
+
"git_url": "git://github.com/apify-projects/store-booking.git",
|
|
77
|
+
"ssh_url": "git@github.com:apify-projects/store-booking.git",
|
|
78
|
+
"clone_url": "https://github.com/apify-projects/store-booking.git",
|
|
79
|
+
"svn_url": "https://github.com/apify-projects/store-booking",
|
|
80
|
+
"homepage": "",
|
|
81
|
+
"size": 3080,
|
|
82
|
+
"stargazers_count": 0,
|
|
83
|
+
"watchers_count": 0,
|
|
84
|
+
"language": "TypeScript",
|
|
85
|
+
"has_issues": true,
|
|
86
|
+
"has_projects": true,
|
|
87
|
+
"has_downloads": true,
|
|
88
|
+
"has_wiki": true,
|
|
89
|
+
"has_pages": false,
|
|
90
|
+
"has_discussions": false,
|
|
91
|
+
"forks_count": 1,
|
|
92
|
+
"mirror_url": null,
|
|
93
|
+
"archived": false,
|
|
94
|
+
"disabled": false,
|
|
95
|
+
"open_issues_count": 19,
|
|
96
|
+
"license": {
|
|
97
|
+
"key": "apache-2.0",
|
|
98
|
+
"name": "Apache License 2.0",
|
|
99
|
+
"spdx_id": "Apache-2.0",
|
|
100
|
+
"url": "https://api.github.com/licenses/apache-2.0",
|
|
101
|
+
"node_id": "MDc6TGljZW5zZTI="
|
|
102
|
+
},
|
|
103
|
+
"allow_forking": true,
|
|
104
|
+
"is_template": false,
|
|
105
|
+
"web_commit_signoff_required": false,
|
|
106
|
+
"topics": [],
|
|
107
|
+
"visibility": "private",
|
|
108
|
+
"forks": 1,
|
|
109
|
+
"open_issues": 19,
|
|
110
|
+
"watchers": 0,
|
|
111
|
+
"default_branch": "master",
|
|
112
|
+
"stargazers": 0,
|
|
113
|
+
"master_branch": "master",
|
|
114
|
+
"organization": "apify-projects",
|
|
115
|
+
"custom_properties": {}
|
|
116
|
+
},
|
|
117
|
+
"pusher": {
|
|
118
|
+
"name": "lhotanok",
|
|
119
|
+
"email": "84460709+lhotanok@users.noreply.github.com"
|
|
120
|
+
},
|
|
121
|
+
"organization": {
|
|
122
|
+
"login": "apify-projects",
|
|
123
|
+
"id": 69005150,
|
|
124
|
+
"node_id": "MDEyOk9yZ2FuaXphdGlvbjY5MDA1MTUw",
|
|
125
|
+
"url": "https://api.github.com/orgs/apify-projects",
|
|
126
|
+
"repos_url": "https://api.github.com/orgs/apify-projects/repos",
|
|
127
|
+
"events_url": "https://api.github.com/orgs/apify-projects/events",
|
|
128
|
+
"hooks_url": "https://api.github.com/orgs/apify-projects/hooks",
|
|
129
|
+
"issues_url": "https://api.github.com/orgs/apify-projects/issues",
|
|
130
|
+
"members_url": "https://api.github.com/orgs/apify-projects/members{/member}",
|
|
131
|
+
"public_members_url": "https://api.github.com/orgs/apify-projects/public_members{/member}",
|
|
132
|
+
"avatar_url": "https://avatars.githubusercontent.com/u/69005150?v=4",
|
|
133
|
+
"description": "This organization houses projects that are not part of Apify itself, such as enterprise or marketplace developed actors and scrapers."
|
|
134
|
+
},
|
|
135
|
+
"sender": {
|
|
136
|
+
"login": "lhotanok",
|
|
137
|
+
"id": 84460709,
|
|
138
|
+
"node_id": "MDQ6VXNlcjg0NDYwNzA5",
|
|
139
|
+
"avatar_url": "https://avatars.githubusercontent.com/u/84460709?v=4",
|
|
140
|
+
"gravatar_id": "",
|
|
141
|
+
"url": "https://api.github.com/users/lhotanok",
|
|
142
|
+
"html_url": "https://github.com/lhotanok",
|
|
143
|
+
"followers_url": "https://api.github.com/users/lhotanok/followers",
|
|
144
|
+
"following_url": "https://api.github.com/users/lhotanok/following{/other_user}",
|
|
145
|
+
"gists_url": "https://api.github.com/users/lhotanok/gists{/gist_id}",
|
|
146
|
+
"starred_url": "https://api.github.com/users/lhotanok/starred{/owner}{/repo}",
|
|
147
|
+
"subscriptions_url": "https://api.github.com/users/lhotanok/subscriptions",
|
|
148
|
+
"organizations_url": "https://api.github.com/users/lhotanok/orgs",
|
|
149
|
+
"repos_url": "https://api.github.com/users/lhotanok/repos",
|
|
150
|
+
"events_url": "https://api.github.com/users/lhotanok/events{/privacy}",
|
|
151
|
+
"received_events_url": "https://api.github.com/users/lhotanok/received_events",
|
|
152
|
+
"type": "User",
|
|
153
|
+
"site_admin": false
|
|
154
|
+
},
|
|
155
|
+
"created": false,
|
|
156
|
+
"deleted": false,
|
|
157
|
+
"forced": false,
|
|
158
|
+
"base_ref": null,
|
|
159
|
+
"compare": "https://github.com/apify-projects/store-booking/compare/6b186d1fa8b6...591b7f55c7b8",
|
|
160
|
+
"commits": [
|
|
161
|
+
{
|
|
162
|
+
"id": "7f9e105e4ce7881f9def76db60bd8e4e58ead9f2",
|
|
163
|
+
"tree_id": "3f16d04f6d1cb44f8b90015a69168fdcc29621e1",
|
|
164
|
+
"distinct": true,
|
|
165
|
+
"message": "fix: A",
|
|
166
|
+
"timestamp": "2024-07-20T02:51:54+02:00",
|
|
167
|
+
"url": "https://github.com/apify-projects/store-booking/commit/591b7f55c7b8feb5012937f870d43266b0650118",
|
|
168
|
+
"author": {
|
|
169
|
+
"name": "Kristýna Lhoťanová",
|
|
170
|
+
"email": "84460709+lhotanok@users.noreply.github.com",
|
|
171
|
+
"username": "oklinov"
|
|
172
|
+
},
|
|
173
|
+
"committer": {
|
|
174
|
+
"name": "GitHub",
|
|
175
|
+
"email": "noreply@github.com",
|
|
176
|
+
"username": "web-flow"
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"id": "3d1556141893eb3b7f281702e5cbec13d8814156",
|
|
181
|
+
"tree_id": "3f16d04f6d1cb44f8b90015a69168fdcc29621e1",
|
|
182
|
+
"distinct": true,
|
|
183
|
+
"message": "feat: A",
|
|
184
|
+
"timestamp": "2024-07-20T02:51:54+02:00",
|
|
185
|
+
"url": "https://github.com/apify-projects/store-booking/commit/591b7f55c7b8feb5012937f870d43266b0650118",
|
|
186
|
+
"author": {
|
|
187
|
+
"name": "Kristýna Lhoťanová",
|
|
188
|
+
"email": "84460709+lhotanok@users.noreply.github.com",
|
|
189
|
+
"username": "oklinov"
|
|
190
|
+
},
|
|
191
|
+
"committer": {
|
|
192
|
+
"name": "GitHub",
|
|
193
|
+
"email": "noreply@github.com",
|
|
194
|
+
"username": "web-flow"
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
],
|
|
198
|
+
"head_commit": {
|
|
199
|
+
"id": "591b7f55c7b8feb5012937f870d43266b0650118",
|
|
200
|
+
"tree_id": "3f16d04f6d1cb44f8b90015a69168fdcc29621e1",
|
|
201
|
+
"distinct": true,
|
|
202
|
+
"message": "fix: validate rendered detail page locale (both language & currency) (#133)",
|
|
203
|
+
"timestamp": "2024-07-20T02:51:54+02:00",
|
|
204
|
+
"url": "https://github.com/apify-projects/store-booking/commit/591b7f55c7b8feb5012937f870d43266b0650118",
|
|
205
|
+
"author": {
|
|
206
|
+
"name": "Kristýna Lhoťanová",
|
|
207
|
+
"email": "84460709+lhotanok@users.noreply.github.com",
|
|
208
|
+
"username": "lhotanok"
|
|
209
|
+
},
|
|
210
|
+
"committer": {
|
|
211
|
+
"name": "GitHub",
|
|
212
|
+
"email": "noreply@github.com",
|
|
213
|
+
"username": "web-flow"
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
package/index.ts
ADDED
package/lib/consts.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ToFinishWithOptionsWithDefaults } from './types';
|
|
2
|
+
|
|
3
|
+
export const TO_FINISH_WITH_OPTIONS: ToFinishWithOptionsWithDefaults = {
|
|
4
|
+
status: 'SUCCEEDED',
|
|
5
|
+
duration: {
|
|
6
|
+
min: 600, // 0.6 sec
|
|
7
|
+
max: 600_000, // 10 min
|
|
8
|
+
},
|
|
9
|
+
failedRequests: 0,
|
|
10
|
+
requestsRetries: { max: 3 },
|
|
11
|
+
forbiddenLogs: [
|
|
12
|
+
'ReferenceError',
|
|
13
|
+
'TypeError',
|
|
14
|
+
],
|
|
15
|
+
};
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import type { Assertion, ExpectStatic } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { TO_FINISH_WITH_OPTIONS } from './consts.js';
|
|
4
|
+
import type { Interval, ToFinishWithOptions } from './types';
|
|
5
|
+
import { RunTestResult } from './run-test-result.js';
|
|
6
|
+
|
|
7
|
+
export const extendExpect = (expect: ExpectStatic): ExpectStatic => {
|
|
8
|
+
expect.extend({
|
|
9
|
+
toBeArray: (actual) => {
|
|
10
|
+
const pass = Array.isArray(actual);
|
|
11
|
+
return {
|
|
12
|
+
pass,
|
|
13
|
+
message: () => `Expected "${actual}" to be a array`,
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
toBeBoolean: (received) => {
|
|
17
|
+
const actual = typeof received;
|
|
18
|
+
const expected = 'boolean';
|
|
19
|
+
return {
|
|
20
|
+
pass: actual === expected,
|
|
21
|
+
message: () => `Expected "${actual}" to be a boolean`,
|
|
22
|
+
expected: 'boolean',
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
toBeEmptyArray: (actual) => {
|
|
26
|
+
const pass = Array.isArray(actual) && actual.length === 0;
|
|
27
|
+
return {
|
|
28
|
+
pass,
|
|
29
|
+
message: () => `Expected ${actual} to be an empty array`,
|
|
30
|
+
actual,
|
|
31
|
+
expected: [],
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
toBeNonEmptyArray: (actual) => {
|
|
35
|
+
const pass = Array.isArray(actual) && actual.length > 0;
|
|
36
|
+
return {
|
|
37
|
+
pass,
|
|
38
|
+
message: () => `Expected ${actual} to be a non-empty array`,
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
toBeNonEmptyString: (actual) => {
|
|
42
|
+
const pass = typeof actual === 'string' && actual.length > 0;
|
|
43
|
+
return {
|
|
44
|
+
pass,
|
|
45
|
+
message: () => `Expected "${actual}" to be a non-empty string`,
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
toBeNumber: (actual) => {
|
|
49
|
+
const pass = typeof actual === 'number';
|
|
50
|
+
return {
|
|
51
|
+
pass,
|
|
52
|
+
message: () => `Expected "${actual}" to be a number`,
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
toBeNonEmptyObject: (actual) => {
|
|
56
|
+
const isObject = typeof actual === 'object' && !Array.isArray(actual);
|
|
57
|
+
const isNonEmpty = Object.entries(actual).length > 0;
|
|
58
|
+
const pass = isObject && isNonEmpty;
|
|
59
|
+
return {
|
|
60
|
+
pass,
|
|
61
|
+
message: () => `Expected "${actual}" to be a non-emtpy object`,
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
toBeObject: (actual) => {
|
|
65
|
+
const pass = typeof actual === 'object' && !Array.isArray(actual);
|
|
66
|
+
return {
|
|
67
|
+
pass,
|
|
68
|
+
message: () => `Expected "${actual}" to be an object`,
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
toBeString: (received) => {
|
|
72
|
+
const actual = typeof received;
|
|
73
|
+
const expected = 'string';
|
|
74
|
+
return {
|
|
75
|
+
pass: actual === expected,
|
|
76
|
+
message: () => `Expected "${actual}" to be a string`,
|
|
77
|
+
actual,
|
|
78
|
+
expected,
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
toBeTrue: (actual) => {
|
|
82
|
+
const pass = actual === true;
|
|
83
|
+
return {
|
|
84
|
+
pass,
|
|
85
|
+
message: () => `Expected "${actual}" to be true`,
|
|
86
|
+
expected: true,
|
|
87
|
+
actual,
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
toBeFalse: (actual) => {
|
|
91
|
+
const pass = actual === false;
|
|
92
|
+
return {
|
|
93
|
+
pass,
|
|
94
|
+
message: () => `Expected "${actual}" to be false`,
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
toBeWholeNumber: (received) => {
|
|
98
|
+
const pass = Number.isInteger(received);
|
|
99
|
+
return {
|
|
100
|
+
pass,
|
|
101
|
+
message: () => `Expected "${received}" to be a whole number`,
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
toBeWithinRange: (received, lower, upper) => {
|
|
105
|
+
const pass = received >= lower && received <= upper;
|
|
106
|
+
return {
|
|
107
|
+
pass,
|
|
108
|
+
message: () => `Expected ${received} to be within range <${lower}, ${upper}>`,
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
toFinishWith: async <PpeEvent extends string>(
|
|
112
|
+
received: RunTestResult,
|
|
113
|
+
userOptions: ToFinishWithOptions<PpeEvent>,
|
|
114
|
+
) => {
|
|
115
|
+
const options = {
|
|
116
|
+
...TO_FINISH_WITH_OPTIONS,
|
|
117
|
+
...userOptions,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const diffs: Diffs = {
|
|
121
|
+
pass: true,
|
|
122
|
+
actual: ['Run:'],
|
|
123
|
+
expected: ['Run:'],
|
|
124
|
+
};
|
|
125
|
+
{
|
|
126
|
+
const expected = options?.status;
|
|
127
|
+
const actual = received.status;
|
|
128
|
+
diffs.pass = expected === actual;
|
|
129
|
+
diffs.actual.push(`status=${actual}`);
|
|
130
|
+
diffs.expected.push(`status=${expected}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const {
|
|
134
|
+
chargedEventCounts,
|
|
135
|
+
stats: { durationMillis },
|
|
136
|
+
} = await received.getRunInfo();
|
|
137
|
+
const datasetItemCount = (await received.getDataset()).items.length;
|
|
138
|
+
const stats = (await received.getStatistics());
|
|
139
|
+
|
|
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');
|
|
144
|
+
|
|
145
|
+
const ppeDiffs: Diffs = {
|
|
146
|
+
pass: true,
|
|
147
|
+
actual: ['PPE Events:'],
|
|
148
|
+
expected: ['PPE Events:'],
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
if (options.chargedEventCounts) {
|
|
152
|
+
const expected = options.chargedEventCounts;
|
|
153
|
+
const actual = chargedEventCounts ?? {};
|
|
154
|
+
const uniquePpeEvents = new Set([
|
|
155
|
+
...Array.from(Object.keys(expected)),
|
|
156
|
+
...Array.from(Object.keys(actual)),
|
|
157
|
+
]);
|
|
158
|
+
|
|
159
|
+
for (const ppeEvent of uniquePpeEvents) {
|
|
160
|
+
if (!(ppeEvent in expected)) {
|
|
161
|
+
isWithinInterval(
|
|
162
|
+
ppeDiffs,
|
|
163
|
+
actual[ppeEvent],
|
|
164
|
+
{ [ppeEvent]: 0 },
|
|
165
|
+
ppeEvent,
|
|
166
|
+
);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (!(ppeEvent in actual)) {
|
|
170
|
+
ppeDiffs.pass = false;
|
|
171
|
+
ppeDiffs.actual.push(`${ppeEvent}=X`);
|
|
172
|
+
ppeDiffs.expected.push(`${ppeEvent}=${expected[ppeEvent as PpeEvent]}`);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
isWithinInterval(ppeDiffs, actual[ppeEvent], expected, ppeEvent as PpeEvent);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
diffs.pass = ppeDiffs.pass;
|
|
179
|
+
diffs.actual.push(ppeDiffs.actual.join('\n '));
|
|
180
|
+
diffs.expected.push(ppeDiffs.expected.join('\n '));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
{
|
|
184
|
+
const { forbiddenLogs } = options;
|
|
185
|
+
const log = await received.getLog();
|
|
186
|
+
const occuredLogs = [];
|
|
187
|
+
for (const forbiddenLog of forbiddenLogs) {
|
|
188
|
+
if (log.includes(forbiddenLog)) {
|
|
189
|
+
occuredLogs.push(forbiddenLog);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (occuredLogs.length > 0) {
|
|
193
|
+
diffs.pass = false;
|
|
194
|
+
diffs.actual.push(` logs=[${occuredLogs.join(', ')}]`);
|
|
195
|
+
diffs.expected.push(` logs=[]`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
pass: diffs.pass,
|
|
201
|
+
message: () => `Run ${received.id} didn't finish as expected`,
|
|
202
|
+
actual: diffs.actual.join('\n '),
|
|
203
|
+
expected: diffs.expected.join('\n '),
|
|
204
|
+
};
|
|
205
|
+
},
|
|
206
|
+
toStartWith: (received, prefix) => {
|
|
207
|
+
const pass = received?.startsWith(prefix);
|
|
208
|
+
return {
|
|
209
|
+
pass,
|
|
210
|
+
message: () => `Expected "${received}" to start with "${prefix}"`,
|
|
211
|
+
};
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// here we are switching logic of expect and expect.soft
|
|
216
|
+
// expect => expect.soft
|
|
217
|
+
// expect.hard => expect
|
|
218
|
+
const { soft } = expect;
|
|
219
|
+
const oldExpect = { ...expect };
|
|
220
|
+
const softExpect = <T>(actual: T, message?: string): Assertion => {
|
|
221
|
+
return soft(actual, message);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
for (const [key, value] of Object.entries(oldExpect)) {
|
|
225
|
+
// @ts-expect-error: No idea how to type this properly
|
|
226
|
+
softExpect[key] = value;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
softExpect.hard = <T>(actual: T, message?: string): Assertion<T> => {
|
|
230
|
+
return expect(actual, message);
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
return softExpect as unknown as ExpectStatic;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
type Diffs = {
|
|
237
|
+
pass: boolean
|
|
238
|
+
actual: string[]
|
|
239
|
+
expected: string[]
|
|
240
|
+
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const isWithinInterval = <T extends string>(
|
|
244
|
+
diffs: Diffs,
|
|
245
|
+
actual: number | undefined,
|
|
246
|
+
options: Record<T, Interval | null>,
|
|
247
|
+
intervalOption: T,
|
|
248
|
+
) => {
|
|
249
|
+
const expected = options[intervalOption];
|
|
250
|
+
if (expected === null) {
|
|
251
|
+
// check is disabled if expected value is null
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (typeof expected === 'number') {
|
|
255
|
+
if (actual !== expected) {
|
|
256
|
+
diffs.pass = false;
|
|
257
|
+
diffs.actual.push(`${intervalOption}=${actual}`);
|
|
258
|
+
diffs.expected.push(`${intervalOption}=${expected}`);
|
|
259
|
+
}
|
|
260
|
+
} else if (typeof expected === 'object') {
|
|
261
|
+
const { min, max } = expected;
|
|
262
|
+
if (actual === undefined || (min !== undefined && actual < min) || (max !== undefined && actual > max)) {
|
|
263
|
+
diffs.pass = false;
|
|
264
|
+
diffs.actual.push(`${intervalOption}=${actual}`);
|
|
265
|
+
diffs.expected.push(`${intervalOption}=<${min ?? ''},${max ?? ''}>`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
};
|
package/lib/lib.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { ApifyClient } from 'apify-client';
|
|
2
|
+
import {
|
|
3
|
+
describe as vitestDescribe,
|
|
4
|
+
ExpectStatic,
|
|
5
|
+
TestFunction,
|
|
6
|
+
test as vitestTest,
|
|
7
|
+
SuiteFactory,
|
|
8
|
+
TestOptions,
|
|
9
|
+
TestContext,
|
|
10
|
+
} from 'vitest';
|
|
11
|
+
import type { ActorBuild, RunOptions } from './types';
|
|
12
|
+
import { RunTestResult } from './run-test-result.js';
|
|
13
|
+
import { extendExpect } from './extend-expect.js';
|
|
14
|
+
import { getActorPrefilledInput } from './utils.js';
|
|
15
|
+
|
|
16
|
+
const ACTOR_BUILDS = 'ACTOR_BUILDS';
|
|
17
|
+
let actorBuilds: ActorBuild[] = [];
|
|
18
|
+
try {
|
|
19
|
+
const actorBuildsEnv = process.env[ACTOR_BUILDS];
|
|
20
|
+
if (actorBuildsEnv) {
|
|
21
|
+
actorBuilds = JSON.parse(actorBuildsEnv);
|
|
22
|
+
if (!Array.isArray(actorBuilds)) {
|
|
23
|
+
throw new Error(`${ACTOR_BUILDS} env variable should contain a JSON array, got ${typeof actorBuilds}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
} catch (err) {
|
|
27
|
+
throw new Error(`Failed to parse actor builds: ${err}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const config = actorBuilds.reduce((map, cfg) => {
|
|
31
|
+
map.set(cfg.actorName, cfg);
|
|
32
|
+
map.set(cfg.actorId, cfg);
|
|
33
|
+
return map;
|
|
34
|
+
}, new Map<string, ActorBuild>());
|
|
35
|
+
|
|
36
|
+
export { ExpectStatic };
|
|
37
|
+
|
|
38
|
+
const { TESTER_APIFY_TOKEN, RUN_PLATFORM_TESTS, RUN_ALL_PLATFORM_TESTS } = process.env;
|
|
39
|
+
const apifyClient = new ApifyClient({ token: TESTER_APIFY_TOKEN });
|
|
40
|
+
|
|
41
|
+
const DEFAULT_TEST_OPTIONS: TestOptions = {
|
|
42
|
+
// we want to run tests concurrently
|
|
43
|
+
concurrent: true,
|
|
44
|
+
// test should finish within 1 hour
|
|
45
|
+
timeout: 60_000 * 60,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const describe = (
|
|
49
|
+
name: string,
|
|
50
|
+
fn?: SuiteFactory<object>,
|
|
51
|
+
options: TestOptions = DEFAULT_TEST_OPTIONS,
|
|
52
|
+
) => {
|
|
53
|
+
vitestDescribe.runIf(!!RUN_PLATFORM_TESTS || !!RUN_ALL_PLATFORM_TESTS)(name, options, fn);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const DEFAULT_TEST_ACTOR_OPTIONS: TestOptions = {
|
|
57
|
+
retry: 1,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const testActor = <T>(
|
|
61
|
+
actorName: string,
|
|
62
|
+
testName: string,
|
|
63
|
+
fn: TestFunction<{ run: ReturnType<typeof createStartRunFn<T>> }>,
|
|
64
|
+
testOptions?: TestOptions,
|
|
65
|
+
) => {
|
|
66
|
+
const options = {
|
|
67
|
+
...DEFAULT_TEST_ACTOR_OPTIONS,
|
|
68
|
+
...testOptions,
|
|
69
|
+
};
|
|
70
|
+
const name = `${actorName}: ${testName}`;
|
|
71
|
+
const shouldRun = !!RUN_ALL_PLATFORM_TESTS || config.has(actorName);
|
|
72
|
+
|
|
73
|
+
vitestTest.runIf(shouldRun)(name, options, async (context) => {
|
|
74
|
+
const { expect, ...rest } = context;
|
|
75
|
+
await fn({
|
|
76
|
+
expect: extendExpect(expect),
|
|
77
|
+
run: createStartRunFn(actorName, context),
|
|
78
|
+
...rest,
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const testTestActor = <T>(
|
|
84
|
+
testName: string,
|
|
85
|
+
fn: TestFunction<{ run: ReturnType<typeof createStartRunFn<T>> }>,
|
|
86
|
+
) => {
|
|
87
|
+
vitestTest(testName, async (context) => {
|
|
88
|
+
const { expect, ...rest } = context;
|
|
89
|
+
await fn({
|
|
90
|
+
expect: extendExpect(expect),
|
|
91
|
+
// @ts-expect-error: this just to test custom matchers
|
|
92
|
+
run: () => { },
|
|
93
|
+
...rest,
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const it = testActor;
|
|
99
|
+
|
|
100
|
+
const createStartRunFn = <T>(actorNameOrId: string, { annotate, task }: TestContext) => {
|
|
101
|
+
const actorConfig = config.get(actorNameOrId);
|
|
102
|
+
const build = actorConfig?.buildNumber;
|
|
103
|
+
const buildId = actorConfig?.buildId;
|
|
104
|
+
return async (runOptions: RunOptions<T>) => {
|
|
105
|
+
const {
|
|
106
|
+
input,
|
|
107
|
+
options,
|
|
108
|
+
prefilledInput,
|
|
109
|
+
} = runOptions;
|
|
110
|
+
const actor = apifyClient.actor(actorNameOrId);
|
|
111
|
+
|
|
112
|
+
const actorInput = {
|
|
113
|
+
...(prefilledInput && await getActorPrefilledInput(apifyClient, actorNameOrId, buildId)),
|
|
114
|
+
...input,
|
|
115
|
+
};
|
|
116
|
+
const run = await actor.call(
|
|
117
|
+
actorInput,
|
|
118
|
+
{ build, ...options, },
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const runLink = `https://console.apify.com/view/runs/${run.id}`;
|
|
122
|
+
await annotate(runLink, 'run_link');
|
|
123
|
+
// @ts-expect-error: `TaskMeta` cannot be retyped
|
|
124
|
+
task.meta = {
|
|
125
|
+
runId: run.id,
|
|
126
|
+
runLink,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// waiting for datasetItemCount and chargedEventCounts to sync
|
|
130
|
+
await new Promise((resolve) => setTimeout(resolve, 10_000));
|
|
131
|
+
|
|
132
|
+
return new RunTestResult(apifyClient, run);
|
|
133
|
+
};
|
|
134
|
+
};
|