apify-test-tools 0.10.2-beta.8 → 0.10.2-beta.9
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/.github/actions/checkout-restore-dependencies/action.yaml +13 -0
- package/.github/cli/apify-test-tools.mjs +1 -1
- package/.github/scripts/check-major-tag-refs.mjs +1 -1
- package/.github/scripts/mask-config-secrets.sh +17 -0
- package/.github/scripts/run-with-apify-tokens.mjs +36 -11
- package/.github/workflows/public_platform-tests-claude-investigate-and-fix.yaml +6 -2
- package/.github/workflows/public_platform-tests.yaml +23 -0
- package/.github/workflows/public_pr-build-test.yaml +30 -1
- package/.github/workflows/public_push-build-latest.yaml +20 -3
- package/CHANGELOG.md +1 -0
- package/CONTRIBUTING.md +99 -8
- package/README.md +218 -699
- package/bin/git.ts +1 -1
- package/dist/bin/git.js +1 -1
- package/dist/bin/git.js.map +1 -1
- package/dist/test/unit/github-scripts/mask-config-secrets.test.d.ts +2 -0
- package/dist/test/unit/github-scripts/mask-config-secrets.test.d.ts.map +1 -0
- package/dist/test/unit/github-scripts/mask-config-secrets.test.js +50 -0
- package/dist/test/unit/github-scripts/mask-config-secrets.test.js.map +1 -0
- package/dist/test/unit/github-scripts/run-with-apify-tokens.test.d.ts +2 -0
- package/dist/test/unit/github-scripts/run-with-apify-tokens.test.d.ts.map +1 -0
- package/dist/test/unit/github-scripts/run-with-apify-tokens.test.js +102 -0
- package/dist/test/unit/github-scripts/run-with-apify-tokens.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/cli.md +358 -0
- package/docs/lib.md +271 -0
- package/docs/workflows.md +366 -0
- package/package.json +1 -1
- package/test/unit/github-scripts/mask-config-secrets.test.ts +61 -0
- package/test/unit/github-scripts/run-with-apify-tokens.test.ts +115 -0
package/docs/cli.md
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
# CLI API
|
|
2
|
+
|
|
3
|
+
The package includes the `apify-test-tools` binary. The [reusable workflows](workflows.md) call it to find changed
|
|
4
|
+
Actors, build them, and report results, and you can run it locally the same way:
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npx apify-test-tools <command> [flags]
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Run every command from the root of the Actor repository; it reads `apify-test-tools.config.json` from there.
|
|
11
|
+
Results are written to stdout as JSON, diagnostics to stderr, so commands can be piped into each other and into `jq`.
|
|
12
|
+
|
|
13
|
+
| Command | Does | Needs |
|
|
14
|
+
| --------------------------------------------------------------------- | ----------------------------------------------------------- | -------------------------- |
|
|
15
|
+
| [`get-affected-actors`](#finding-affected-actors-get-affected-actors) | Lists the Actors changed in a commit range | git only |
|
|
16
|
+
| [`build`](#building-actors-build) | Builds the Actors given on stdin from the pushed branch | Actor tokens |
|
|
17
|
+
| [`build-from-local`](#build-from-local-source-build-from-local) | Builds Actors from local files | Actor tokens |
|
|
18
|
+
| [`notify-release`](#announcing-a-release-notify-release) | Announces a release on Slack | `SLACK_TOKEN_RELEASES_BOT` |
|
|
19
|
+
| [`report-tests`](#reporting-test-results-report-tests) | Summarizes a vitest JSON report and posts failures to Slack | `SLACK_TOKEN_TESTS_BOT` |
|
|
20
|
+
| [`delete-old-builds`](#deleting-old-builds-delete-old-builds) | Deletes old builds of the Actors | Actor tokens |
|
|
21
|
+
| [`get-actor-configs`](#listing-actors-get-actor-configs) | Prints the resolved config | nothing |
|
|
22
|
+
|
|
23
|
+
"Actor tokens" are the environment variables named by each Actor's `tokenEnvVar` in the config. A command only needs
|
|
24
|
+
the tokens of the Actors it touches.
|
|
25
|
+
|
|
26
|
+
## Global flags
|
|
27
|
+
|
|
28
|
+
| Flag | Default | Meaning |
|
|
29
|
+
| ------------- | ------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| `--log-level` | `info` | Minimum level written to stderr: `debug`, `info`, `warn`, `error`, or `silent`. Results on stdout are unaffected |
|
|
31
|
+
|
|
32
|
+
## Selecting Actors (`--actors`, `--ignore`)
|
|
33
|
+
|
|
34
|
+
Commands that read the config take `--actors` and `--ignore`, each a list of `actorFullName`s (space-separated,
|
|
35
|
+
or the flag repeated). `--actors` keeps only the listed Actors, `--ignore` drops them. An unknown
|
|
36
|
+
name is an error rather than silently selecting nothing.
|
|
37
|
+
|
|
38
|
+
## Dry runs (`--dry-run`)
|
|
39
|
+
|
|
40
|
+
Every command that changes something (`build`, `build-from-local`, `notify-release`, `delete-old-builds`,
|
|
41
|
+
`report-tests`) takes `--dry-run`. A dry run:
|
|
42
|
+
|
|
43
|
+
- **Changes nothing.** No versions or builds are created, nothing is uploaded or deleted, and no Slack message is
|
|
44
|
+
posted.
|
|
45
|
+
- **Does every read and check the real run does**, so it fails wherever the real run would: change detection, the
|
|
46
|
+
Apify token and Actor lookup, the [repository check](#repository-check), collecting and zipping local sources, and
|
|
47
|
+
the Slack token (verified with Slack's read-only `auth.test`).
|
|
48
|
+
- **Needs the same tokens as the real run.** Read-only tokens are enough, since nothing is written. A dry run can't
|
|
49
|
+
tell whether a token is also allowed to write, because the Apify API has no dry-run mode of its own.
|
|
50
|
+
- **Prints a plan, not results.** Logs start with `[DRY RUN] Would …`. `build` and `build-from-local` write
|
|
51
|
+
`{ "dryRun": true, "builds": [...] }` to stdout instead of the build array, so the plan can't be passed to tests as
|
|
52
|
+
`ACTOR_BUILDS` by mistake.
|
|
53
|
+
|
|
54
|
+
## Change detection
|
|
55
|
+
|
|
56
|
+
When a PR is opened or code is pushed, the tool determines which Actors need to be built and tested based on the
|
|
57
|
+
changed files. For each changed file, for each Actor:
|
|
58
|
+
|
|
59
|
+
1. **Sibling exclusion** — files inside another Actor's `folder` are excluded first. This prevents an Actor with
|
|
60
|
+
broad context from being triggered by changes that belong to a sibling Actor.
|
|
61
|
+
2. **CHANGELOG classification** — a `CHANGELOG.md` file is always `cosmetic` (only triggers a release build, not
|
|
62
|
+
tests), for every Actor, regardless of context or folder. (See
|
|
63
|
+
[issue #106](https://github.com/apify/apify-test-tools/issues/106).)
|
|
64
|
+
3. **Context matching** — the file must fall within one of the Actor's context paths (`dockerContextDir` from
|
|
65
|
+
`actor.json` by default, or `overrideActorContext` from config if set). Files outside every context path are
|
|
66
|
+
skipped.
|
|
67
|
+
4. **Hardcoded ignore list, context-aware** — the file path is first "hoisted" relative to the context path it
|
|
68
|
+
matched (e.g. a standalone Actor's own `.eslintrc` is checked as just `.eslintrc`, not the full
|
|
69
|
+
repo-root-relative path), then checked against repo-level dev file patterns (`.vscode/`, `.gitignore`, `.husky/`,
|
|
70
|
+
`.eslintrc`, `eslint.config.mjs`, `.prettierrc`, `.editorconfig`). There's no hardcoded special-casing for legacy
|
|
71
|
+
`code/`/`shared/` layouts — repos that need those directories treated as top-level must list them explicitly in
|
|
72
|
+
`overrideActorContext`.
|
|
73
|
+
5. **`.dockerignore` filtering** — if a `.dockerignore` exists at the root of the Actor's `dockerContextDir`,
|
|
74
|
+
matching files are ignored. Patterns are resolved relative to `dockerContextDir`, matching Docker's own behavior.
|
|
75
|
+
Test files are the exception: `get-affected-actors --test-files-glob <glob>` (relative to the repo root) skips
|
|
76
|
+
this check for matching files, so a changed test triggers a build, and with it its tests, even when
|
|
77
|
+
`.dockerignore` lists it. The PR workflow passes `test/platform/**`. Context matching still applies, so tests
|
|
78
|
+
outside an Actor's `dockerContextDir` (e.g. a root `test/` with standalone Actors) need to be listed in its
|
|
79
|
+
`overrideActorContext`; `get-affected-actors` logs a warning for changed test files outside every Actor's context.
|
|
80
|
+
6. **README classification** — a `README.md` file is `cosmetic` (only triggers a release build, not tests) if it
|
|
81
|
+
lives inside the Actor's own `folder`; otherwise it's ignored entirely, since it isn't documentation for this
|
|
82
|
+
Actor.
|
|
83
|
+
7. **Cosmetic JSON classification** — `.json` files inside the Actor's own `.actor/` directory with only cosmetic
|
|
84
|
+
schema changes (whitespace, key ordering) only trigger a release build.
|
|
85
|
+
8. **Functional** — everything else triggers both build and tests.
|
|
86
|
+
|
|
87
|
+
## Finding affected Actors (`get-affected-actors`)
|
|
88
|
+
|
|
89
|
+
`get-affected-actors` lists the Actors whose context changed in a commit range, using the rules in
|
|
90
|
+
[Change detection](#change-detection). It only reads git and the config file, so it needs no tokens. The range always
|
|
91
|
+
ends at the checked-out `HEAD`, so check out the branch you want to inspect first.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# A PR: the branch's own changes, narrowed to the Actors that changed since its last validated commit
|
|
95
|
+
npx apify-test-tools get-affected-actors --base origin/master --since <sha>
|
|
96
|
+
|
|
97
|
+
# A release: everything after the last released commit
|
|
98
|
+
npx apify-test-tools get-affected-actors --since <sha>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
| Flag | Default | Meaning |
|
|
102
|
+
| ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
103
|
+
| `--base` | | Only what `HEAD` changes compared to this branch counts, like `git diff <base>...HEAD` |
|
|
104
|
+
| `--since` | | Only changes after this commit count (see below) |
|
|
105
|
+
| `--test-files-glob` | | Test files (glob from the repo root) that count as changes even when `.dockerignore` lists them, see [Change detection](#change-detection) |
|
|
106
|
+
| `--actors` | all | Only report these Actors |
|
|
107
|
+
| `--ignore` | none | Never report these Actors |
|
|
108
|
+
|
|
109
|
+
Pass `--base`, `--since`, or both:
|
|
110
|
+
|
|
111
|
+
- **`--base` only** inspects what the branch changes, the same files GitHub's "Files changed" tab shows: the diff
|
|
112
|
+
from where the branch forked from `--base`, or the last commit of `--base` it merged in. What it merged in from
|
|
113
|
+
`--base` is already validated there, so it doesn't count.
|
|
114
|
+
- **`--base` and `--since`** also inspects what changed after the branch's last validated commit, and reports only
|
|
115
|
+
the Actors that changed in both. An Actor in only one of them is already validated, by this branch or on `--base`.
|
|
116
|
+
When both changed an Actor, the changes can interact, so it is reported, but as functional only when both changes
|
|
117
|
+
are functional. A validated functional change followed by README edits is cosmetic, and so is a functional
|
|
118
|
+
change merged in from `--base` that meets only cosmetic changes of the branch. `--since` doesn't have to be on
|
|
119
|
+
the branch, so a rebase only re-checks the Actors that `--base` changed too. If `--since` is not in the local
|
|
120
|
+
history (a force-push), or it is `HEAD` (a rerun), it is ignored and the whole branch is checked.
|
|
121
|
+
- **`--since` only** inspects everything after it up to `HEAD`. It must be an ancestor of `HEAD` and fails
|
|
122
|
+
otherwise, see [Releasing Actors](#releasing-actors). If `HEAD` is `--since`, nothing is reported.
|
|
123
|
+
|
|
124
|
+
`commits`, `changedFiles` and `changelog` describe the whole range, `--base...HEAD` or `--since..HEAD`: `--since`
|
|
125
|
+
together with `--base` only narrows the Actor lists.
|
|
126
|
+
|
|
127
|
+
The output is a JSON document:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"version": 1,
|
|
132
|
+
"head": "<sha of HEAD>",
|
|
133
|
+
"commits": [{ "sha": "…", "author": "…", "date": "…", "message": "…" }],
|
|
134
|
+
"changedFiles": ["src/main.ts", "CHANGELOG.md"],
|
|
135
|
+
"changelog": "Lines added to CHANGELOG.md, or null",
|
|
136
|
+
"functionallyChangedActors": [{ "actorFullName": "john.doe/my-actor" }],
|
|
137
|
+
"cosmeticallyChangedActors": [{ "actorFullName": "john.doe/other-actor" }]
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Each changed Actor is in exactly one of the lists. `functionallyChangedActors` have at least one functional change,
|
|
142
|
+
so they need a new build and tests. `cosmeticallyChangedActors` only have cosmetic changes (see
|
|
143
|
+
[Change detection](#change-detection)), so they need a new build only when released: the PR workflow builds
|
|
144
|
+
`functionallyChangedActors`, the release workflow builds both.
|
|
145
|
+
|
|
146
|
+
## Building Actors (`build`)
|
|
147
|
+
|
|
148
|
+
`build` builds exactly the Actors it is given on stdin, from the checked-out branch as it is on the remote, and
|
|
149
|
+
prints the builds as the same JSON array `build-from-local` prints. A detached HEAD is an error, since there is no
|
|
150
|
+
branch to build from. It doesn't look at git history: which Actors to build is up to you, usually picked from the
|
|
151
|
+
output of [`get-affected-actors`](#finding-affected-actors-get-affected-actors).
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# A PR: the Actors a branch changed functionally, built into the test version
|
|
155
|
+
npx apify-test-tools get-affected-actors --base origin/master \
|
|
156
|
+
| jq '{ actors: .functionallyChangedActors }' \
|
|
157
|
+
| npx apify-test-tools build --version-number 0.99
|
|
158
|
+
|
|
159
|
+
# A release: every Actor changed since the last release, into the version users run
|
|
160
|
+
npx apify-test-tools get-affected-actors --since <sha> \
|
|
161
|
+
| jq '{ actors: (.functionallyChangedActors + .cosmeticallyChangedActors) }' \
|
|
162
|
+
| npx apify-test-tools build --version-number default
|
|
163
|
+
|
|
164
|
+
# One Actor, whatever changed
|
|
165
|
+
echo '{ "actors": [{ "actorFullName": "john.doe/my-actor" }] }' | npx apify-test-tools build --version-number 0.99
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The input is `{ "actors": [{ "actorFullName": "<owner/name>" }] }`; other fields are ignored. An empty `actors` list
|
|
169
|
+
builds nothing and prints `[]`. Empty or invalid input is an error, so a failed command earlier in the pipe can't turn
|
|
170
|
+
into a successful run that built nothing. In GitHub Actions, give such a step `shell: bash` so the pipe also fails on
|
|
171
|
+
its own (the default shell has no `pipefail`).
|
|
172
|
+
|
|
173
|
+
| Flag | Default | Meaning |
|
|
174
|
+
| ------------------ | ------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
175
|
+
| `--version-number` | required | Version to build, like `0.99`, or `default` for each Actor's default version (the one its default build tag points to) |
|
|
176
|
+
| `--repo-url` | the `origin` remote | Repository to build from, see [Repository check](#repository-check) |
|
|
177
|
+
| `--dry-run` | `false` | See [Dry runs](#dry-runs---dry-run) |
|
|
178
|
+
|
|
179
|
+
The output is the build array the vitest library reads from `ACTOR_BUILDS`:
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
[{ "buildId": "...", "actorRawId": "...", "buildNumber": "...", "actorFullName": "john.doe/my-actor" }]
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
`build` never sets or changes build tags. A build gets the tag its version is configured with on the platform, so
|
|
186
|
+
building the default version moves its tag (usually `latest`) to the new build, and building a version without a
|
|
187
|
+
tag leaves every tag where it is. Builds always use the Docker cache.
|
|
188
|
+
|
|
189
|
+
### Repository check
|
|
190
|
+
|
|
191
|
+
`build` points each Actor's version at the `origin` remote. Because a build overwrites the version's Git URL,
|
|
192
|
+
building from a fork or a mirror would repoint the Actor. So, before building, the remote is compared with the Git
|
|
193
|
+
URL of the Actor's default version (the one its default build tag points to), and the command fails if they differ
|
|
194
|
+
or if the default version isn't built from a Git repository. The URL form doesn't matter
|
|
195
|
+
(`https://github.com/org/repo` equals `git@github.com:org/repo.git`).
|
|
196
|
+
|
|
197
|
+
To build from a different repository on purpose, e.g. when moving an Actor, pass `--repo-url`. That skips the check.
|
|
198
|
+
|
|
199
|
+
## Build from local source (`build-from-local`)
|
|
200
|
+
|
|
201
|
+
Builds Actors directly from your local files, with no push needed: the sources are zipped into the Actor's
|
|
202
|
+
`actor-<id>-source` key-value store and built as `TARBALL`, like `apify push`. It prints the same build array as
|
|
203
|
+
`build`.
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
APIFY_TOKEN_JOHN_DOE=<token> npx apify-test-tools build-from-local --actors john.doe/my-actor
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
| Flag | Default | Meaning |
|
|
210
|
+
| ----------- | ------- | ------------------------------------------------------------------------------- |
|
|
211
|
+
| `--actors` | all | Only build these Actors. Point vitest at their tests only, for quicker feedback |
|
|
212
|
+
| `--ignore` | none | Never build these Actors |
|
|
213
|
+
| `--dry-run` | `false` | Check the token, the Actor and the zipped sources without uploading or building |
|
|
214
|
+
|
|
215
|
+
## Releasing Actors
|
|
216
|
+
|
|
217
|
+
A release is three commands: find what changed since the last released commit, build those Actors into their
|
|
218
|
+
default version (the one users run), and announce it.
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
npx apify-test-tools get-affected-actors --since <sha> > affected.json
|
|
222
|
+
|
|
223
|
+
jq '{ actors: (.functionallyChangedActors + .cosmeticallyChangedActors) }' affected.json \
|
|
224
|
+
| npx apify-test-tools build --version-number default --dry-run
|
|
225
|
+
|
|
226
|
+
npx apify-test-tools notify-release --release-slack-channel "#releases" --dry-run < affected.json
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### The last released commit (`--since`)
|
|
230
|
+
|
|
231
|
+
The last commit that is **already released**. Everything after it, up to `HEAD`, is released. Git alone can't tell
|
|
232
|
+
which commits a push brought, and guessing wrong silently leaves Actors on old code, so it has to be passed.
|
|
233
|
+
|
|
234
|
+
The changed files come from diffing `--since` and `HEAD` directly, so this works for every merge strategy as long as
|
|
235
|
+
`--since` is the branch tip from **before** the push:
|
|
236
|
+
|
|
237
|
+
| Merge strategy | What lands on the branch | What to pass |
|
|
238
|
+
| ------------------------------ | --------------------------------- | ---------------------------------------------------------------------- |
|
|
239
|
+
| Squash and merge | 1 new commit | the push's "before" commit (`HEAD~1` also works) |
|
|
240
|
+
| Merge commit | the PR's commits + 1 merge commit | the push's "before" commit (`HEAD^1`, the first parent, also works) |
|
|
241
|
+
| Rebase and merge / direct push | N new commits | the push's "before" commit (`HEAD~1` would miss the first N-1 commits) |
|
|
242
|
+
|
|
243
|
+
On GitHub, the push's "before" commit is `${{ github.event.before }}`, which the reusable workflow passes for you.
|
|
244
|
+
On other CIs use the equivalent, e.g. `$CI_COMMIT_BEFORE_SHA` on GitLab. The checkout needs the full history
|
|
245
|
+
(`fetch-depth: 0` in `actions/checkout`) so the commit is available locally.
|
|
246
|
+
|
|
247
|
+
`get-affected-actors --since` without `--base` fails instead of guessing when:
|
|
248
|
+
|
|
249
|
+
- the commit is all zeros (GitHub sends this for a push that created the branch)
|
|
250
|
+
- the commit isn't in the local history (shallow checkout) or isn't an ancestor of `HEAD` (the branch was
|
|
251
|
+
force-pushed)
|
|
252
|
+
|
|
253
|
+
In those cases, rerun it with a `--since` you pick yourself: an ancestor of `HEAD` from before the changes you want
|
|
254
|
+
to release (for a shallow checkout, fetch the full history instead). `--actors` only narrows which of the changed
|
|
255
|
+
Actors are reported; it never forces a build, so on its own it doesn't get past these errors. If `HEAD` is
|
|
256
|
+
`--since`, nothing is reported, so nothing is built or announced.
|
|
257
|
+
|
|
258
|
+
A failed release isn't retried by the next push: that push's base commit is the failed one's head, so its changes
|
|
259
|
+
are skipped. Re-run the failed workflow run, or release the Actors manually.
|
|
260
|
+
|
|
261
|
+
## Announcing a release (`notify-release`)
|
|
262
|
+
|
|
263
|
+
`notify-release` reads the output of `get-affected-actors` on stdin. It posts the changelog additions to
|
|
264
|
+
`--release-slack-channel` and the commits and changed files to `--report-slack-channel`, and posts nothing when there
|
|
265
|
+
are no new commits. It names the release after the `origin` remote (or `--repo-url`) and the author of the newest
|
|
266
|
+
commit, and needs `SLACK_TOKEN_RELEASES_BOT`.
|
|
267
|
+
|
|
268
|
+
| Flag | Meaning |
|
|
269
|
+
| ------------------------- | ------------------------------------------------------ |
|
|
270
|
+
| `--release-slack-channel` | Channel for the changelog additions only |
|
|
271
|
+
| `--report-slack-channel` | Channel for the full report: commits and changed files |
|
|
272
|
+
| `--repo-url` | Repository to name the release after |
|
|
273
|
+
| `--dry-run` | See [Dry runs](#dry-runs---dry-run) |
|
|
274
|
+
|
|
275
|
+
## Reporting test results (`report-tests`)
|
|
276
|
+
|
|
277
|
+
Reads a vitest JSON report (`--reporter=json --outputFile=<file>`), logs the passed and failed tests with their run
|
|
278
|
+
links, and posts the failures to Slack. Without `--report-slack-channel` it only logs; with it,
|
|
279
|
+
`SLACK_TOKEN_TESTS_BOT` must be set, even when no test failed.
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
npx vitest --run --reporter=default --reporter=json --outputFile=./test-output.json test/platform
|
|
283
|
+
npx apify-test-tools report-tests --report-file ./test-output.json --report-slack-channel "#my-channel"
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
| Flag | Default | Meaning |
|
|
287
|
+
| ------------------------ | -------- | --------------------------------------------- |
|
|
288
|
+
| `--report-file` | required | Path to the vitest JSON report |
|
|
289
|
+
| `--report-slack-channel` | | Channel to post the failures to |
|
|
290
|
+
| `--job-url` | | Link to the CI job, included in the message |
|
|
291
|
+
| `--workflow-name` | | Name of the workflow, included in the message |
|
|
292
|
+
| `--dry-run` | `false` | See [Dry runs](#dry-runs---dry-run) |
|
|
293
|
+
|
|
294
|
+
## Deleting old builds (`delete-old-builds`)
|
|
295
|
+
|
|
296
|
+
Deletes builds older than 30 days of every selected Actor. It never deletes the default build, or a build tagged
|
|
297
|
+
`latest`, `version…`, `v-…` or `v0`–`v9…`. A build tagged `devel` is kept for only 7 days.
|
|
298
|
+
|
|
299
|
+
| Flag | Default | Meaning |
|
|
300
|
+
| ----------- | ------- | ----------------------------------------------------------- |
|
|
301
|
+
| `--actors` | all | Only these Actors |
|
|
302
|
+
| `--ignore` | none | Never these Actors |
|
|
303
|
+
| `--dry-run` | `false` | List the builds that would be deleted without deleting them |
|
|
304
|
+
|
|
305
|
+
## Listing Actors (`get-actor-configs`)
|
|
306
|
+
|
|
307
|
+
Prints the [config](../README.md#config-file) as the tool resolves it: one entry per Actor, with the group's values
|
|
308
|
+
applied and `actor.json` read. Takes `--actors` and `--ignore`. Useful to check a config change.
|
|
309
|
+
|
|
310
|
+
## Running locally
|
|
311
|
+
|
|
312
|
+
Running the tests locally is useful when you only want to update the testing code in `test/`, because you can
|
|
313
|
+
iterate on it without pushing new code to the remote. If you only change tests, skip the builds entirely and run
|
|
314
|
+
them against the default builds, see [Running tests locally](lib.md#running-tests-locally).
|
|
315
|
+
|
|
316
|
+
To test changed Actor source, you need builds of it. The quickest way is
|
|
317
|
+
[`build-from-local`](#build-from-local-source-build-from-local), which needs no push:
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
BUILDS=$(APIFY_TOKEN_JOHN_DOE=apify_api_xxx \
|
|
321
|
+
npx apify-test-tools build-from-local --actors john.doe/my-actor)
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
To build from git, like CI does:
|
|
325
|
+
|
|
326
|
+
1. Switch to a dummy branch that you will push and can later delete
|
|
327
|
+
2. `npm i apify-test-tools@latest -D`
|
|
328
|
+
3. Push your code (changes you want to test)
|
|
329
|
+
4. Build the Actors on Apify (with your new code)
|
|
330
|
+
5. Run tests against those builds. You can change tests and run on the same builds.
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
BUILDS=$(npx apify-test-tools get-affected-actors --base origin/master --test-files-glob 'test/platform/**' \
|
|
334
|
+
| jq '{ actors: .functionallyChangedActors }' \
|
|
335
|
+
| APIFY_TOKEN_JOHN_DOE=apify_api_xxx npx apify-test-tools build --version-number 0.99)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Run it with your dummy branch checked out: it builds the Actors that branch changed functionally, from the branch as
|
|
339
|
+
it is on the remote, into the test version `0.99`. Add `--dry-run` to `build` to print the plan (the version and Git
|
|
340
|
+
URL each Actor would be built from) first, and update `--base` to the branch you compare against. Tests only run for
|
|
341
|
+
Actors in `ACTOR_BUILDS`, so pass `--test-files-glob` to count test changes even when `.dockerignore` lists them.
|
|
342
|
+
|
|
343
|
+
If you want to test against existing builds instead, skip the build and write the build array by hand, only for the
|
|
344
|
+
Actors you need to test.
|
|
345
|
+
|
|
346
|
+
Then run the tests against the builds:
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
ACTOR_BUILDS="$BUILDS" \
|
|
350
|
+
TESTER_APIFY_TOKEN=apify_api_yyy \
|
|
351
|
+
npx vitest --run --maxConcurrency 20 --fileParallelism=true --maxWorkers 100 test/platform
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Legacy git commands
|
|
355
|
+
|
|
356
|
+
`get-commits`, `get-latest-commit` and `get-changed-files` print the commits, the newest commit, or the changed files
|
|
357
|
+
on `--source-branch` since it diverged from `--target-branch` (optionally only after `--base-commit`). They predate
|
|
358
|
+
`get-affected-actors`, which reports the same data, and are kept for older workflows.
|
package/docs/lib.md
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# Vitest library API
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
import { describe, testActor, testStandbyActor, RunTestResult, ExpectStatic } from 'apify-test-tools';
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
Requires `vitest` `3.2.4` or later as a peer dependency.
|
|
8
|
+
|
|
9
|
+
## Environment variables
|
|
10
|
+
|
|
11
|
+
The library reads these when the test file is loaded:
|
|
12
|
+
|
|
13
|
+
| Variable | Description |
|
|
14
|
+
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| `TESTER_APIFY_TOKEN` | Token the tests start runs and read their data with. Without it (and without `RUN_ALL_PLATFORM_TESTS`), every `describe` block is skipped, so a plain `npm test` stays unaffected. |
|
|
16
|
+
| `ACTOR_BUILDS` | JSON array of builds, as printed by the CLI's [`build`](cli.md#building-actors-build) and [`build-from-local`](cli.md#build-from-local-source-build-from-local). Only tests of Actors listed here run, and they run against the listed build. |
|
|
17
|
+
| `RUN_ALL_PLATFORM_TESTS` | Run every test, not only those of Actors in `ACTOR_BUILDS`. Actors missing from `ACTOR_BUILDS` run their default build. The scheduled platform tests set it. |
|
|
18
|
+
|
|
19
|
+
`ACTOR_BUILDS` entries look like this, and are matched to tests by `actorFullName` or `actorRawId`:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
[{ "buildId": "...", "actorRawId": "...", "buildNumber": "1.2.3", "actorFullName": "john.doe/my-actor" }]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## `describe(name, fn, options?)`
|
|
26
|
+
|
|
27
|
+
Same as vitest's `describe`, but skipped unless `TESTER_APIFY_TOKEN` or `RUN_ALL_PLATFORM_TESTS` is set. Its tests
|
|
28
|
+
run concurrently by default. Use it instead of vitest's `describe` for platform tests.
|
|
29
|
+
|
|
30
|
+
## `testActor(actorId, testName, fn, options?)`
|
|
31
|
+
|
|
32
|
+
Registers a test for an Actor. `actorId` is the Actor's full name (`owner/name`) or its raw platform ID. The test
|
|
33
|
+
only runs when the Actor is in `ACTOR_BUILDS` or `RUN_ALL_PLATFORM_TESTS` is set. Its name is
|
|
34
|
+
`<actorId>: <testName>`.
|
|
35
|
+
|
|
36
|
+
`fn` gets the vitest test context, with `expect` extended by the [custom matchers](#custom-matchers), and a `run`
|
|
37
|
+
function.
|
|
38
|
+
|
|
39
|
+
`expect` is soft: a failed assertion marks the test as failed but the test keeps going, so one run reports every
|
|
40
|
+
failed check at once. Use `expect.hard(...)` for an assertion that must stop the test, e.g. before indexing into a
|
|
41
|
+
list that may be empty.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { describe, testActor } from 'apify-test-tools';
|
|
45
|
+
|
|
46
|
+
describe('test', () => {
|
|
47
|
+
testActor(actorId, 'actor test 1', async ({ expect, run }) => {
|
|
48
|
+
const runResult = await run({ input });
|
|
49
|
+
|
|
50
|
+
// your checks
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
testActor(actorId, 'actor test 2', async ({ expect, run }) => {
|
|
54
|
+
const runResult = await run({ input });
|
|
55
|
+
|
|
56
|
+
// your checks
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Test options
|
|
62
|
+
|
|
63
|
+
The fourth argument takes vitest's test options:
|
|
64
|
+
|
|
65
|
+
| Option | Default |
|
|
66
|
+
| ------------ | ------- |
|
|
67
|
+
| `timeout` | 1 hour |
|
|
68
|
+
| `retry` | `1` |
|
|
69
|
+
| `concurrent` | `true` |
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
testActor(
|
|
73
|
+
actorId,
|
|
74
|
+
'slow actor test',
|
|
75
|
+
async ({ expect, run }) => {
|
|
76
|
+
const runResult = await run({ input });
|
|
77
|
+
await expect(runResult).toFinishWith({ datasetItemCount: 100 });
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
timeout: 2 * 60 * 60 * 1000, // 2 hours
|
|
81
|
+
retry: 3,
|
|
82
|
+
},
|
|
83
|
+
);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### `run(options)`
|
|
87
|
+
|
|
88
|
+
Starts the Actor, waits for the run to finish, and returns a [`RunTestResult`](#runtestresult). It runs the build
|
|
89
|
+
from `ACTOR_BUILDS`, or the Actor's default build when it isn't listed there. Before returning, it waits 20 seconds
|
|
90
|
+
for the run's dataset and statistics to sync, since the platform is only eventually consistent. The run's Console
|
|
91
|
+
link is added to the test as a vitest annotation.
|
|
92
|
+
|
|
93
|
+
| Option | Description |
|
|
94
|
+
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
95
|
+
| `input` | Actor input |
|
|
96
|
+
| `prefilledInput` | When `true`, `input` is merged on top of the Actor's prefilled input |
|
|
97
|
+
| `options` | [Actor call options](https://docs.apify.com/api/client/js/reference/interface/ActorCallOptions) like `memory`. `timeout` is in seconds and defaults to the Actor's default run timeout, at most 59 minutes, so the run finishes before the test times out |
|
|
98
|
+
| `runId` | Don't start a run; return this existing run instead. Every other option is ignored |
|
|
99
|
+
|
|
100
|
+
#### Using prefilled input
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
testActor(actorId, 'with prefilled input', async ({ expect, run }) => {
|
|
104
|
+
const runResult = await run({
|
|
105
|
+
prefilledInput: true,
|
|
106
|
+
input: { maxItems: 10 }, // merged on top of the prefilled input
|
|
107
|
+
});
|
|
108
|
+
await expect(runResult).toFinishWith({ datasetItemCount: 10 });
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### Testing an existing run
|
|
113
|
+
|
|
114
|
+
Useful for iterating on the checks of a test without waiting for a new run:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
testActor(actorId, 'validate existing run', async ({ expect, run }) => {
|
|
118
|
+
const runResult = await run({ runId: 'some-run-id' });
|
|
119
|
+
await expect(runResult).toFinishWith({ datasetItemCount: 100 });
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## `testStandbyActor(actorId, testName, fn, options?)`
|
|
124
|
+
|
|
125
|
+
Same as `testActor`, for Actors in standby mode. It creates a temporary task with the build from `ACTOR_BUILDS` (or
|
|
126
|
+
the Actor's standby build) and deletes it after the test. Instead of `run`, `fn` gets `callStandby`, which sends a
|
|
127
|
+
`POST` request with `input` as the JSON body to the task's standby URL, plus `path`, and returns
|
|
128
|
+
`{ data, status, headers }`, where `data` is the parsed JSON response.
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { describe, testStandbyActor } from 'apify-test-tools';
|
|
132
|
+
|
|
133
|
+
describe('standby tests', () => {
|
|
134
|
+
testStandbyActor(actorId, 'standby request', async ({ expect, callStandby }) => {
|
|
135
|
+
const { data, status } = await callStandby({
|
|
136
|
+
input: { query: 'test' },
|
|
137
|
+
path: '/search',
|
|
138
|
+
headers: { 'Content-Type': 'application/json' },
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
expect(status).toBe(200);
|
|
142
|
+
expect(data.results).toBeNonEmptyArray();
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
It fails if the Actor doesn't support standby mode.
|
|
148
|
+
|
|
149
|
+
## `RunTestResult`
|
|
150
|
+
|
|
151
|
+
What `run` returns. Every getter fetches its data on the first call and returns the same result afterwards, so you
|
|
152
|
+
can call them freely.
|
|
153
|
+
|
|
154
|
+
| Member | Returns |
|
|
155
|
+
| -------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
156
|
+
| `id` | The run ID |
|
|
157
|
+
| `status` | The run status when it finished |
|
|
158
|
+
| `getDataset<T>()` | `{ items: T[] }`, every item of the default dataset |
|
|
159
|
+
| `getLog()` | The run log as a string |
|
|
160
|
+
| `getStatistics()` | The crawler statistics from the `SDK_CRAWLER_STATISTICS_0` record, or `undefined` for non-crawler Actors |
|
|
161
|
+
| `getInput<T>()` | The `INPUT` record of the default key-value store |
|
|
162
|
+
| `getKeyValueStoreClient()` | An `apify-client` client for the default key-value store |
|
|
163
|
+
| `getRunInfo()` | The run object, fetched again so it reflects changes after the run finished |
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
testActor(actorId, 'check dataset items', async ({ expect, run }) => {
|
|
167
|
+
const runResult = await run({ input });
|
|
168
|
+
|
|
169
|
+
const { items } = await runResult.getDataset();
|
|
170
|
+
expect(items[0].title).toBeNonEmptyString();
|
|
171
|
+
|
|
172
|
+
const log = await runResult.getLog();
|
|
173
|
+
expect(log).toContain('Crawl finished');
|
|
174
|
+
|
|
175
|
+
const stats = await runResult.getStatistics();
|
|
176
|
+
expect(stats?.requestsFinished).toBeGreaterThan(0);
|
|
177
|
+
|
|
178
|
+
const kvs = runResult.getKeyValueStoreClient();
|
|
179
|
+
const record = await kvs.getRecord('OUTPUT');
|
|
180
|
+
|
|
181
|
+
const runInfo = await runResult.getRunInfo();
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## `toFinishWith(options)`
|
|
186
|
+
|
|
187
|
+
Validates the common run properties in a single call and reports every failed check at once. Only
|
|
188
|
+
`datasetItemCount` is required; the rest have defaults. Numeric checks take an exact number or a range: `{ min }`,
|
|
189
|
+
`{ max }` or `{ min, max }`. Set a check with a default to `null` to skip it.
|
|
190
|
+
|
|
191
|
+
| Option | Default | Checks |
|
|
192
|
+
| ---------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
193
|
+
| `datasetItemCount` | required | Number of items in the default dataset |
|
|
194
|
+
| `status` | `'SUCCEEDED'` | Run status |
|
|
195
|
+
| `duration` | `{ min: 600, max: 600_000 }` | Run duration in milliseconds (0.6 s to 10 min) |
|
|
196
|
+
| `failedRequests` | `0` | Failed requests in the crawler statistics |
|
|
197
|
+
| `requestsRetries` | `{ max: 3 }` | Request retries in the crawler statistics |
|
|
198
|
+
| `maxRetriesPerRequest` | `null` | The most retries any single request needed |
|
|
199
|
+
| `forbiddenLogs` | `['ReferenceError', 'TypeError']` | Strings that must not appear in the log |
|
|
200
|
+
| `chargedEventCounts` | not checked | Pay-per-event counts, per event name. An event you omit is expected to have been charged 0 times |
|
|
201
|
+
|
|
202
|
+
`failedRequests` and `requestsRetries` come from the crawler statistics, so they fail for an Actor that doesn't
|
|
203
|
+
write them. Set both to `null` for Actors that don't use a Crawlee crawler.
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
await expect(runResult).toFinishWith({
|
|
207
|
+
datasetItemCount: { min: 80, max: 120 },
|
|
208
|
+
duration: { max: 30 * 60 * 1000 },
|
|
209
|
+
chargedEventCounts: {
|
|
210
|
+
'actor-start': 1,
|
|
211
|
+
'place-scraped': { min: 80, max: 120 },
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Custom matchers
|
|
217
|
+
|
|
218
|
+
`expect` in `testActor` and `testStandbyActor` is extended with:
|
|
219
|
+
|
|
220
|
+
- `toBeArray()` / `toBeEmptyArray()` / `toBeNonEmptyArray()`
|
|
221
|
+
- `toBeString()` / `toBeNonEmptyString()` / `toStartWith(prefix)`
|
|
222
|
+
- `toBeNumber()` / `toBeWholeNumber()` / `toBeWithinRange(min, max)`
|
|
223
|
+
- `toBeBoolean()` / `toBeTrue()` / `toBeFalse()`
|
|
224
|
+
- `toBeObject()` / `toBeNonEmptyObject()`
|
|
225
|
+
- [`toFinishWith(options)`](#tofinishwithoptions)
|
|
226
|
+
|
|
227
|
+
Pass a message as the second argument of `expect` to say what failed:
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
expect(place.title, `London Eye's title`).toEqual('lastminute.com London Eye');
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Custom validation functions
|
|
234
|
+
|
|
235
|
+
Wrap shared checks in your own functions, e.g. in `test/platform/utils.ts`, and pass them `expect`. Use the
|
|
236
|
+
`ExpectStatic` type so the custom matchers type-check:
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
import { ExpectStatic } from 'apify-test-tools';
|
|
240
|
+
|
|
241
|
+
export const validateItem = (expect: ExpectStatic, item: any) => {
|
|
242
|
+
expect(item.title, 'Item title').toBeString();
|
|
243
|
+
};
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Running tests locally
|
|
247
|
+
|
|
248
|
+
Run every command from the root of the Actor repository.
|
|
249
|
+
|
|
250
|
+
**Against the current default builds.** Enough when you only change the tests:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
RUN_ALL_PLATFORM_TESTS=1 TESTER_APIFY_TOKEN=<token> npx vitest --run test/platform
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**Against builds of your changes.** Build the Actors with the CLI, either from local files with
|
|
257
|
+
[`build-from-local`](cli.md#build-from-local-source-build-from-local) or from a pushed branch with
|
|
258
|
+
[`build`](cli.md#building-actors-build), and pass the output as `ACTOR_BUILDS`:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
BUILDS=$(APIFY_TOKEN_JOHN_DOE=<token> npx apify-test-tools build-from-local --actors john.doe/my-actor)
|
|
262
|
+
|
|
263
|
+
ACTOR_BUILDS="$BUILDS" \
|
|
264
|
+
TESTER_APIFY_TOKEN=<token> \
|
|
265
|
+
npx vitest --run --maxConcurrency 20 --fileParallelism=true --maxWorkers 100 test/platform
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
You can change the tests and re-run them against the same builds.
|
|
269
|
+
|
|
270
|
+
`TESTER_APIFY_TOKEN` can be the token of any account with enough memory for the runs. To run only some tests, point vitest at a specific file instead of `test/platform`, or filter
|
|
271
|
+
by name with `-t`. See [Running locally](cli.md#running-locally) for the full flow.
|