apify-test-tools 0.10.2-beta.1 → 0.10.2-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/.gitattributes +2 -0
  2. package/.github/actionlint.yaml +11 -0
  3. package/.github/actions/checkout-restore-dependencies/action.yaml +64 -28
  4. package/.github/cli/apify-test-tools.mjs +360 -0
  5. package/.github/scripts/mask-config-secrets.sh +17 -0
  6. package/.github/scripts/run-with-apify-tokens.mjs +37 -12
  7. package/.github/workflows/_check_code.yaml +25 -16
  8. package/.github/workflows/_move_major_tag.yaml +5 -83
  9. package/.github/workflows/_tests.yaml +34 -0
  10. package/.github/workflows/_update_release_metadata.yaml +0 -12
  11. package/.github/workflows/manual_move_major_tag.yaml +3 -3
  12. package/.github/workflows/manual_release_stable.yaml +3 -13
  13. package/.github/workflows/on_master.yaml +9 -5
  14. package/.github/workflows/on_pull_request.yaml +3 -48
  15. package/.github/workflows/public_platform-tests-claude-investigate-and-fix.yaml +16 -3
  16. package/.github/workflows/public_platform-tests.yaml +27 -3
  17. package/.github/workflows/public_pr-build-test.yaml +67 -29
  18. package/.github/workflows/public_push-build-latest.yaml +44 -10
  19. package/.github/workflows/public_review.yaml +5 -4
  20. package/.husky/pre-commit +5 -0
  21. package/.prettierignore +3 -0
  22. package/CHANGELOG.md +17 -0
  23. package/CONTRIBUTING.md +112 -24
  24. package/README.md +218 -600
  25. package/bin/affected-actors.ts +198 -0
  26. package/bin/build-from-local.ts +65 -13
  27. package/bin/build.ts +123 -41
  28. package/bin/diff-changes.ts +32 -23
  29. package/bin/diff-json-schema.ts +5 -6
  30. package/bin/git.ts +69 -99
  31. package/bin/main.ts +96 -135
  32. package/bin/slack.ts +43 -10
  33. package/bin/stdin-input.ts +65 -0
  34. package/bin/test-report.ts +6 -9
  35. package/bin/types.ts +15 -0
  36. package/dist/bin/affected-actors.d.ts +46 -0
  37. package/dist/bin/affected-actors.d.ts.map +1 -0
  38. package/dist/bin/affected-actors.js +135 -0
  39. package/dist/bin/affected-actors.js.map +1 -0
  40. package/dist/bin/build-from-local.d.ts +12 -3
  41. package/dist/bin/build-from-local.d.ts.map +1 -1
  42. package/dist/bin/build-from-local.js +34 -12
  43. package/dist/bin/build-from-local.js.map +1 -1
  44. package/dist/bin/build.d.ts +30 -7
  45. package/dist/bin/build.d.ts.map +1 -1
  46. package/dist/bin/build.js +85 -26
  47. package/dist/bin/build.js.map +1 -1
  48. package/dist/bin/diff-changes.d.ts +11 -4
  49. package/dist/bin/diff-changes.d.ts.map +1 -1
  50. package/dist/bin/diff-changes.js +19 -19
  51. package/dist/bin/diff-changes.js.map +1 -1
  52. package/dist/bin/diff-json-schema.d.ts +2 -2
  53. package/dist/bin/diff-json-schema.d.ts.map +1 -1
  54. package/dist/bin/diff-json-schema.js +1 -4
  55. package/dist/bin/diff-json-schema.js.map +1 -1
  56. package/dist/bin/git.d.ts +23 -21
  57. package/dist/bin/git.d.ts.map +1 -1
  58. package/dist/bin/git.js +60 -83
  59. package/dist/bin/git.js.map +1 -1
  60. package/dist/bin/main.js +82 -105
  61. package/dist/bin/main.js.map +1 -1
  62. package/dist/bin/slack.d.ts +14 -4
  63. package/dist/bin/slack.d.ts.map +1 -1
  64. package/dist/bin/slack.js +39 -6
  65. package/dist/bin/slack.js.map +1 -1
  66. package/dist/bin/stdin-input.d.ts +17 -0
  67. package/dist/bin/stdin-input.d.ts.map +1 -0
  68. package/dist/bin/stdin-input.js +54 -0
  69. package/dist/bin/stdin-input.js.map +1 -0
  70. package/dist/bin/test-report.d.ts.map +1 -1
  71. package/dist/bin/test-report.js +6 -8
  72. package/dist/bin/test-report.js.map +1 -1
  73. package/dist/bin/types.d.ts +13 -0
  74. package/dist/bin/types.d.ts.map +1 -1
  75. package/dist/lib/extend-expect.d.ts.map +1 -1
  76. package/dist/lib/extend-expect.js +37 -218
  77. package/dist/lib/extend-expect.js.map +1 -1
  78. package/dist/lib/lib.d.ts +1 -2
  79. package/dist/lib/lib.d.ts.map +1 -1
  80. package/dist/lib/lib.js +15 -22
  81. package/dist/lib/lib.js.map +1 -1
  82. package/dist/lib/run-checks.d.ts +14 -0
  83. package/dist/lib/run-checks.d.ts.map +1 -0
  84. package/dist/lib/run-checks.js +82 -0
  85. package/dist/lib/run-checks.js.map +1 -0
  86. package/dist/lib/run-test-result.d.ts +3 -6
  87. package/dist/lib/run-test-result.d.ts.map +1 -1
  88. package/dist/lib/run-test-result.js +41 -54
  89. package/dist/lib/run-test-result.js.map +1 -1
  90. package/dist/test/bundle/cli-bundle.test.d.ts +2 -0
  91. package/dist/test/bundle/cli-bundle.test.d.ts.map +1 -0
  92. package/dist/test/bundle/cli-bundle.test.js +190 -0
  93. package/dist/test/bundle/cli-bundle.test.js.map +1 -0
  94. package/dist/test/e2e/cli-bundle.e2e.test.d.ts +2 -0
  95. package/dist/test/e2e/cli-bundle.e2e.test.d.ts.map +1 -0
  96. package/dist/test/e2e/cli-bundle.e2e.test.js +138 -0
  97. package/dist/test/e2e/cli-bundle.e2e.test.js.map +1 -0
  98. package/dist/test/e2e/git-build-release.e2e.test.d.ts +2 -0
  99. package/dist/test/e2e/git-build-release.e2e.test.d.ts.map +1 -0
  100. package/dist/test/e2e/git-build-release.e2e.test.js +87 -0
  101. package/dist/test/e2e/git-build-release.e2e.test.js.map +1 -0
  102. package/dist/test/e2e/helpers.d.ts +37 -0
  103. package/dist/test/e2e/helpers.d.ts.map +1 -0
  104. package/dist/test/e2e/helpers.js +57 -0
  105. package/dist/test/e2e/helpers.js.map +1 -0
  106. package/dist/test/unit/bin/affected-actors-cli.test.d.ts +2 -0
  107. package/dist/test/unit/bin/affected-actors-cli.test.d.ts.map +1 -0
  108. package/dist/test/unit/bin/affected-actors-cli.test.js +167 -0
  109. package/dist/test/unit/bin/affected-actors-cli.test.js.map +1 -0
  110. package/dist/test/unit/bin/affected-actors.test.d.ts +2 -0
  111. package/dist/test/unit/bin/affected-actors.test.d.ts.map +1 -0
  112. package/dist/test/unit/bin/affected-actors.test.js +141 -0
  113. package/dist/test/unit/bin/affected-actors.test.js.map +1 -0
  114. package/dist/test/unit/bin/build-cli.test.d.ts +2 -0
  115. package/dist/test/unit/bin/build-cli.test.d.ts.map +1 -0
  116. package/dist/test/unit/bin/build-cli.test.js +44 -0
  117. package/dist/test/unit/bin/build-cli.test.js.map +1 -0
  118. package/dist/test/unit/bin/build-from-local.test.js +65 -1
  119. package/dist/test/unit/bin/build-from-local.test.js.map +1 -1
  120. package/dist/test/unit/bin/build.test.js +223 -3
  121. package/dist/test/unit/bin/build.test.js.map +1 -1
  122. package/dist/test/unit/bin/compiled-cli.d.ts +6 -0
  123. package/dist/test/unit/bin/compiled-cli.d.ts.map +1 -0
  124. package/dist/test/unit/bin/compiled-cli.js +16 -0
  125. package/dist/test/unit/bin/compiled-cli.js.map +1 -0
  126. package/dist/test/unit/bin/diff-changes.test.js +130 -101
  127. package/dist/test/unit/bin/diff-changes.test.js.map +1 -1
  128. package/dist/test/unit/bin/diff.test.js +25 -28
  129. package/dist/test/unit/bin/diff.test.js.map +1 -1
  130. package/dist/test/unit/bin/git.test.js +69 -152
  131. package/dist/test/unit/bin/git.test.js.map +1 -1
  132. package/dist/test/unit/bin/slack.test.d.ts +2 -0
  133. package/dist/test/unit/bin/slack.test.d.ts.map +1 -0
  134. package/dist/test/unit/bin/slack.test.js +71 -0
  135. package/dist/test/unit/bin/slack.test.js.map +1 -0
  136. package/dist/test/unit/bin/stdin-input.test.d.ts +2 -0
  137. package/dist/test/unit/bin/stdin-input.test.d.ts.map +1 -0
  138. package/dist/test/unit/bin/stdin-input.test.js +50 -0
  139. package/dist/test/unit/bin/stdin-input.test.js.map +1 -0
  140. package/dist/test/unit/github-scripts/mask-config-secrets.test.d.ts +2 -0
  141. package/dist/test/unit/github-scripts/mask-config-secrets.test.d.ts.map +1 -0
  142. package/dist/test/unit/github-scripts/mask-config-secrets.test.js +50 -0
  143. package/dist/test/unit/github-scripts/mask-config-secrets.test.js.map +1 -0
  144. package/dist/test/unit/github-scripts/run-with-apify-tokens.test.d.ts +2 -0
  145. package/dist/test/unit/github-scripts/run-with-apify-tokens.test.d.ts.map +1 -0
  146. package/dist/test/unit/github-scripts/run-with-apify-tokens.test.js +102 -0
  147. package/dist/test/unit/github-scripts/run-with-apify-tokens.test.js.map +1 -0
  148. package/dist/test/unit/run-checks.test.d.ts +2 -0
  149. package/dist/test/unit/run-checks.test.d.ts.map +1 -0
  150. package/dist/test/unit/run-checks.test.js +62 -0
  151. package/dist/test/unit/run-checks.test.js.map +1 -0
  152. package/dist/test/unit/run-test-result.test.d.ts +2 -0
  153. package/dist/test/unit/run-test-result.test.d.ts.map +1 -0
  154. package/dist/test/unit/run-test-result.test.js +34 -0
  155. package/dist/test/unit/run-test-result.test.js.map +1 -0
  156. package/dist/test/unit/should-built-and-test.test.js +41 -38
  157. package/dist/test/unit/should-built-and-test.test.js.map +1 -1
  158. package/dist/tsconfig.tsbuildinfo +1 -1
  159. package/docs/cli.md +373 -0
  160. package/docs/lib.md +271 -0
  161. package/docs/workflows.md +370 -0
  162. package/lib/extend-expect.ts +48 -258
  163. package/lib/lib.ts +24 -28
  164. package/lib/run-checks.ts +101 -0
  165. package/lib/run-test-result.ts +45 -55
  166. package/package.json +13 -3
  167. package/test/bundle/cli-bundle.test.ts +235 -0
  168. package/test/e2e/cli-bundle.e2e.test.ts +207 -0
  169. package/test/e2e/git-build-release.e2e.test.ts +113 -0
  170. package/test/e2e/helpers.ts +82 -0
  171. package/test/unit/bin/affected-actors-cli.test.ts +201 -0
  172. package/test/unit/bin/affected-actors.test.ts +182 -0
  173. package/test/unit/bin/build-cli.test.ts +55 -0
  174. package/test/unit/bin/build-from-local.test.ts +71 -0
  175. package/test/unit/bin/build.test.ts +279 -3
  176. package/test/unit/bin/compiled-cli.ts +17 -0
  177. package/test/unit/bin/diff-changes.test.ts +139 -101
  178. package/test/unit/bin/diff.test.ts +25 -28
  179. package/test/unit/bin/git.test.ts +85 -176
  180. package/test/unit/bin/slack.test.ts +84 -0
  181. package/test/unit/bin/stdin-input.test.ts +61 -0
  182. package/test/unit/github-scripts/mask-config-secrets.test.ts +61 -0
  183. package/test/unit/github-scripts/run-with-apify-tokens.test.ts +115 -0
  184. package/test/unit/run-checks.test.ts +76 -0
  185. package/test/unit/run-test-result.test.ts +44 -0
  186. package/test/unit/should-built-and-test.test.ts +46 -39
  187. package/.github/scripts/check-major-tag-refs.mjs +0 -72
  188. package/.github/scripts/check-package-version-bump.mjs +0 -74
  189. package/.github/workflows-package-version +0 -1
package/docs/cli.md ADDED
@@ -0,0 +1,373 @@
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
+ | `--require-env-vars-in-sync` | `false` | Fail unless the built version's env var names match the default version's, see [Env var check](#env-var-check) |
178
+ | `--dry-run` | `false` | See [Dry runs](#dry-runs---dry-run) |
179
+
180
+ The output is the build array the vitest library reads from `ACTOR_BUILDS`:
181
+
182
+ ```json
183
+ [{ "buildId": "...", "actorRawId": "...", "buildNumber": "...", "actorFullName": "john.doe/my-actor" }]
184
+ ```
185
+
186
+ `build` never sets or changes build tags. A build gets the tag its version is configured with on the platform, so
187
+ building the default version moves its tag (usually `latest`) to the new build, and building a version without a
188
+ tag leaves every tag where it is. Builds always use the Docker cache.
189
+
190
+ ### Repository check
191
+
192
+ `build` points each Actor's version at the `origin` remote. Because a build overwrites the version's Git URL,
193
+ building from a fork or a mirror would repoint the Actor. So, before building, the remote is compared with the Git
194
+ URL of the Actor's default version (the one its default build tag points to), and the command fails if they differ
195
+ or if the default version isn't built from a Git repository. The URL form doesn't matter
196
+ (`https://github.com/org/repo` equals `git@github.com:org/repo.git`).
197
+
198
+ To build from a different repository on purpose, e.g. when moving an Actor, pass `--repo-url`. That skips the check.
199
+
200
+ ### Env var check
201
+
202
+ Tests should run the Actor with the same env vars users run it with. Pass `--require-env-vars-in-sync` to `build` or
203
+ `build-from-local` to compare the env vars of the version they build (e.g. `0.99`, or `0.98` for `build-from-local`)
204
+ with those of the default version, and fail if a name is missing or extra. Only names are compared, since the API
205
+ doesn't return secret values. A version that doesn't exist yet counts as having no env vars, so if the default
206
+ version has any, create the test version on the platform with the same env vars first. A dry run checks the env vars
207
+ too.
208
+
209
+ The CLI leaves it off by default, so a one-off build, e.g. of a debug branch, isn't held up by it.
210
+ [`public_pr-build-test.yaml`](workflows.md#public_pr-build-testyaml) turns it on by default; set its
211
+ `require-env-vars-in-sync` input to `false` to turn it off.
212
+
213
+ ## Build from local source (`build-from-local`)
214
+
215
+ Builds Actors directly from your local files, with no push needed: the sources are zipped into the Actor's
216
+ `actor-<id>-source` key-value store and built as `TARBALL`, like `apify push`. It prints the same build array as
217
+ `build`.
218
+
219
+ ```bash
220
+ APIFY_TOKEN_JOHN_DOE=<token> npx apify-test-tools build-from-local --actors john.doe/my-actor
221
+ ```
222
+
223
+ | Flag | Default | Meaning |
224
+ | ---------------------------- | ------- | -------------------------------------------------------------------------------------------------------- |
225
+ | `--actors` | all | Only build these Actors. Point vitest at their tests only, for quicker feedback |
226
+ | `--ignore` | none | Never build these Actors |
227
+ | `--require-env-vars-in-sync` | `false` | Fail unless the env var names of `0.98` match the default version's, see [Env var check](#env-var-check) |
228
+ | `--dry-run` | `false` | Check the token, the Actor and the zipped sources without uploading or building |
229
+
230
+ ## Releasing Actors
231
+
232
+ A release is three commands: find what changed since the last released commit, build those Actors into their
233
+ default version (the one users run), and announce it.
234
+
235
+ ```bash
236
+ npx apify-test-tools get-affected-actors --since <sha> > affected.json
237
+
238
+ jq '{ actors: (.functionallyChangedActors + .cosmeticallyChangedActors) }' affected.json \
239
+ | npx apify-test-tools build --version-number default --dry-run
240
+
241
+ npx apify-test-tools notify-release --release-slack-channel "#releases" --dry-run < affected.json
242
+ ```
243
+
244
+ ### The last released commit (`--since`)
245
+
246
+ The last commit that is **already released**. Everything after it, up to `HEAD`, is released. Git alone can't tell
247
+ which commits a push brought, and guessing wrong silently leaves Actors on old code, so it has to be passed.
248
+
249
+ The changed files come from diffing `--since` and `HEAD` directly, so this works for every merge strategy as long as
250
+ `--since` is the branch tip from **before** the push:
251
+
252
+ | Merge strategy | What lands on the branch | What to pass |
253
+ | ------------------------------ | --------------------------------- | ---------------------------------------------------------------------- |
254
+ | Squash and merge | 1 new commit | the push's "before" commit (`HEAD~1` also works) |
255
+ | Merge commit | the PR's commits + 1 merge commit | the push's "before" commit (`HEAD^1`, the first parent, also works) |
256
+ | Rebase and merge / direct push | N new commits | the push's "before" commit (`HEAD~1` would miss the first N-1 commits) |
257
+
258
+ On GitHub, the push's "before" commit is `${{ github.event.before }}`, which the reusable workflow passes for you.
259
+ On other CIs use the equivalent, e.g. `$CI_COMMIT_BEFORE_SHA` on GitLab. The checkout needs the full history
260
+ (`fetch-depth: 0` in `actions/checkout`) so the commit is available locally.
261
+
262
+ `get-affected-actors --since` without `--base` fails instead of guessing when:
263
+
264
+ - the commit is all zeros (GitHub sends this for a push that created the branch)
265
+ - the commit isn't in the local history (shallow checkout) or isn't an ancestor of `HEAD` (the branch was
266
+ force-pushed)
267
+
268
+ In those cases, rerun it with a `--since` you pick yourself: an ancestor of `HEAD` from before the changes you want
269
+ to release (for a shallow checkout, fetch the full history instead). `--actors` only narrows which of the changed
270
+ Actors are reported; it never forces a build, so on its own it doesn't get past these errors. If `HEAD` is
271
+ `--since`, nothing is reported, so nothing is built or announced.
272
+
273
+ A failed release isn't retried by the next push: that push's base commit is the failed one's head, so its changes
274
+ are skipped. Re-run the failed workflow run, or release the Actors manually.
275
+
276
+ ## Announcing a release (`notify-release`)
277
+
278
+ `notify-release` reads the output of `get-affected-actors` on stdin. It posts the changelog additions to
279
+ `--release-slack-channel` and the commits and changed files to `--report-slack-channel`, and posts nothing when there
280
+ are no new commits. It names the release after the `origin` remote (or `--repo-url`) and the author of the newest
281
+ commit, and needs `SLACK_TOKEN_RELEASES_BOT`.
282
+
283
+ | Flag | Meaning |
284
+ | ------------------------- | ------------------------------------------------------ |
285
+ | `--release-slack-channel` | Channel for the changelog additions only |
286
+ | `--report-slack-channel` | Channel for the full report: commits and changed files |
287
+ | `--repo-url` | Repository to name the release after |
288
+ | `--dry-run` | See [Dry runs](#dry-runs---dry-run) |
289
+
290
+ ## Reporting test results (`report-tests`)
291
+
292
+ Reads a vitest JSON report (`--reporter=json --outputFile=<file>`), logs the passed and failed tests with their run
293
+ links, and posts the failures to Slack. Without `--report-slack-channel` it only logs; with it,
294
+ `SLACK_TOKEN_TESTS_BOT` must be set, even when no test failed.
295
+
296
+ ```bash
297
+ npx vitest --run --reporter=default --reporter=json --outputFile=./test-output.json test/platform
298
+ npx apify-test-tools report-tests --report-file ./test-output.json --report-slack-channel "#my-channel"
299
+ ```
300
+
301
+ | Flag | Default | Meaning |
302
+ | ------------------------ | -------- | --------------------------------------------- |
303
+ | `--report-file` | required | Path to the vitest JSON report |
304
+ | `--report-slack-channel` | | Channel to post the failures to |
305
+ | `--job-url` | | Link to the CI job, included in the message |
306
+ | `--workflow-name` | | Name of the workflow, included in the message |
307
+ | `--dry-run` | `false` | See [Dry runs](#dry-runs---dry-run) |
308
+
309
+ ## Deleting old builds (`delete-old-builds`)
310
+
311
+ Deletes builds older than 30 days of every selected Actor. It never deletes the default build, or a build tagged
312
+ `latest`, `version…`, `v-…` or `v0`–`v9…`. A build tagged `devel` is kept for only 7 days.
313
+
314
+ | Flag | Default | Meaning |
315
+ | ----------- | ------- | ----------------------------------------------------------- |
316
+ | `--actors` | all | Only these Actors |
317
+ | `--ignore` | none | Never these Actors |
318
+ | `--dry-run` | `false` | List the builds that would be deleted without deleting them |
319
+
320
+ ## Listing Actors (`get-actor-configs`)
321
+
322
+ Prints the [config](../README.md#config-file) as the tool resolves it: one entry per Actor, with the group's values
323
+ applied and `actor.json` read. Takes `--actors` and `--ignore`. Useful to check a config change.
324
+
325
+ ## Running locally
326
+
327
+ Running the tests locally is useful when you only want to update the testing code in `test/`, because you can
328
+ iterate on it without pushing new code to the remote. If you only change tests, skip the builds entirely and run
329
+ them against the default builds, see [Running tests locally](lib.md#running-tests-locally).
330
+
331
+ To test changed Actor source, you need builds of it. The quickest way is
332
+ [`build-from-local`](#build-from-local-source-build-from-local), which needs no push:
333
+
334
+ ```bash
335
+ BUILDS=$(APIFY_TOKEN_JOHN_DOE=apify_api_xxx \
336
+ npx apify-test-tools build-from-local --actors john.doe/my-actor)
337
+ ```
338
+
339
+ To build from git, like CI does:
340
+
341
+ 1. Switch to a dummy branch that you will push and can later delete
342
+ 2. `npm i apify-test-tools@latest -D`
343
+ 3. Push your code (changes you want to test)
344
+ 4. Build the Actors on Apify (with your new code)
345
+ 5. Run tests against those builds. You can change tests and run on the same builds.
346
+
347
+ ```bash
348
+ BUILDS=$(npx apify-test-tools get-affected-actors --base origin/master --test-files-glob 'test/platform/**' \
349
+ | jq '{ actors: .functionallyChangedActors }' \
350
+ | APIFY_TOKEN_JOHN_DOE=apify_api_xxx npx apify-test-tools build --version-number 0.99)
351
+ ```
352
+
353
+ Run it with your dummy branch checked out: it builds the Actors that branch changed functionally, from the branch as
354
+ it is on the remote, into the test version `0.99`. Add `--dry-run` to `build` to print the plan (the version and Git
355
+ URL each Actor would be built from) first, and update `--base` to the branch you compare against. Tests only run for
356
+ Actors in `ACTOR_BUILDS`, so pass `--test-files-glob` to count test changes even when `.dockerignore` lists them.
357
+
358
+ If you want to test against existing builds instead, skip the build and write the build array by hand, only for the
359
+ Actors you need to test.
360
+
361
+ Then run the tests against the builds:
362
+
363
+ ```bash
364
+ ACTOR_BUILDS="$BUILDS" \
365
+ TESTER_APIFY_TOKEN=apify_api_yyy \
366
+ npx vitest --run --maxConcurrency 20 --fileParallelism=true --maxWorkers 100 test/platform
367
+ ```
368
+
369
+ ## Legacy git commands
370
+
371
+ `get-commits`, `get-latest-commit` and `get-changed-files` print the commits, the newest commit, or the changed files
372
+ on `--source-branch` since it diverged from `--target-branch` (optionally only after `--base-commit`). They predate
373
+ `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.