apify-test-tools 0.10.2-beta.19 → 0.10.2-beta.20

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 (75) hide show
  1. package/.github/cli/apify-test-tools.mjs +114 -114
  2. package/.github/scripts/run-with-apify-tokens.mjs +6 -6
  3. package/.github/workflows/public_platform-tests-claude-investigate-and-fix.yaml +1 -1
  4. package/.github/workflows/public_platform-tests.yaml +6 -8
  5. package/.github/workflows/public_pr-build-test.yaml +11 -11
  6. package/.github/workflows/public_push-build-latest.yaml +1 -0
  7. package/CHANGELOG.md +1 -0
  8. package/CONTRIBUTING.md +1 -1
  9. package/README.md +10 -6
  10. package/bin/affected-actors.ts +20 -7
  11. package/bin/diff-changes.ts +40 -16
  12. package/bin/main.ts +12 -19
  13. package/bin/run-tests.ts +29 -4
  14. package/bin/test-files-glob.ts +126 -0
  15. package/bin/utils/config/load-config.ts +4 -1
  16. package/bin/utils/config/parser.ts +24 -1
  17. package/dist/bin/affected-actors.d.ts +13 -4
  18. package/dist/bin/affected-actors.d.ts.map +1 -1
  19. package/dist/bin/affected-actors.js +11 -4
  20. package/dist/bin/affected-actors.js.map +1 -1
  21. package/dist/bin/diff-changes.d.ts +12 -3
  22. package/dist/bin/diff-changes.d.ts.map +1 -1
  23. package/dist/bin/diff-changes.js +25 -12
  24. package/dist/bin/diff-changes.js.map +1 -1
  25. package/dist/bin/main.js +11 -17
  26. package/dist/bin/main.js.map +1 -1
  27. package/dist/bin/run-tests.d.ts +3 -3
  28. package/dist/bin/run-tests.d.ts.map +1 -1
  29. package/dist/bin/run-tests.js +24 -2
  30. package/dist/bin/run-tests.js.map +1 -1
  31. package/dist/bin/test-files-glob.d.ts +18 -0
  32. package/dist/bin/test-files-glob.d.ts.map +1 -0
  33. package/dist/bin/test-files-glob.js +108 -0
  34. package/dist/bin/test-files-glob.js.map +1 -0
  35. package/dist/bin/utils/config/load-config.d.ts +3 -0
  36. package/dist/bin/utils/config/load-config.d.ts.map +1 -1
  37. package/dist/bin/utils/config/load-config.js +3 -1
  38. package/dist/bin/utils/config/load-config.js.map +1 -1
  39. package/dist/bin/utils/config/parser.d.ts +9 -0
  40. package/dist/bin/utils/config/parser.d.ts.map +1 -1
  41. package/dist/bin/utils/config/parser.js +13 -1
  42. package/dist/bin/utils/config/parser.js.map +1 -1
  43. package/dist/test/bundle/cli-bundle.test.js +1 -1
  44. package/dist/test/bundle/cli-bundle.test.js.map +1 -1
  45. package/dist/test/e2e/cli-bundle.e2e.test.js +1 -1
  46. package/dist/test/e2e/cli-bundle.e2e.test.js.map +1 -1
  47. package/dist/test/unit/bin/actor-filtering.test.js +7 -0
  48. package/dist/test/unit/bin/actor-filtering.test.js.map +1 -1
  49. package/dist/test/unit/bin/affected-actors-cli.test.js +59 -2
  50. package/dist/test/unit/bin/affected-actors-cli.test.js.map +1 -1
  51. package/dist/test/unit/bin/affected-actors.test.js +18 -6
  52. package/dist/test/unit/bin/affected-actors.test.js.map +1 -1
  53. package/dist/test/unit/bin/diff-changes.test.js +44 -19
  54. package/dist/test/unit/bin/diff-changes.test.js.map +1 -1
  55. package/dist/test/unit/bin/test-cli.test.js +34 -3
  56. package/dist/test/unit/bin/test-cli.test.js.map +1 -1
  57. package/dist/test/unit/bin/test-files-glob.test.d.ts +2 -0
  58. package/dist/test/unit/bin/test-files-glob.test.d.ts.map +1 -0
  59. package/dist/test/unit/bin/test-files-glob.test.js +102 -0
  60. package/dist/test/unit/bin/test-files-glob.test.js.map +1 -0
  61. package/dist/test/unit/bin/utils/config/parser.test.js +16 -1
  62. package/dist/test/unit/bin/utils/config/parser.test.js.map +1 -1
  63. package/dist/tsconfig.tsbuildinfo +1 -1
  64. package/docs/cli.md +46 -29
  65. package/docs/workflows.md +53 -20
  66. package/package.json +1 -1
  67. package/test/bundle/cli-bundle.test.ts +1 -1
  68. package/test/e2e/cli-bundle.e2e.test.ts +9 -5
  69. package/test/unit/bin/actor-filtering.test.ts +8 -0
  70. package/test/unit/bin/affected-actors-cli.test.ts +73 -3
  71. package/test/unit/bin/affected-actors.test.ts +49 -10
  72. package/test/unit/bin/diff-changes.test.ts +48 -19
  73. package/test/unit/bin/test-cli.test.ts +41 -3
  74. package/test/unit/bin/test-files-glob.test.ts +118 -0
  75. package/test/unit/bin/utils/config/parser.test.ts +24 -1
package/docs/cli.md CHANGED
@@ -71,10 +71,12 @@ changed files. For each changed file, for each Actor:
71
71
  get no special treatment: to treat them as top-level, list them in `overrideActorContext`.
72
72
  5. **`.dockerignore` filtering** — if a `.dockerignore` exists at the root of the Actor's `dockerContextDir`,
73
73
  matching files are ignored. Patterns are resolved relative to `dockerContextDir`, matching Docker's own behavior.
74
- Test files are the exception: `get-affected-actors --test-files-glob <glob>` (relative to the repo root) skips
75
- this check for matching files, so a changed test triggers a build, and with it its tests, even when
76
- `.dockerignore` lists it. The PR workflow passes `test/platform/**`. Context matching still applies, so tests
77
- outside an Actor's `dockerContextDir` (e.g. a root `test/` with standalone Actors) need to be listed in its
74
+ Platform test files are the exception, see the next step.
75
+ **Platform test files** — a file that matches `platformTestsGlob` from the config file (a glob from the repo root,
76
+ default `test/platform/**`, helpers included) is a `test` change, also when `.dockerignore` lists it. This is
77
+ checked before the `.dockerignore` filtering. A test change puts the Actor in `testChangedActors`, so its tests
78
+ run, but it is neither functional nor cosmetic, so a release doesn't build it. Context matching still applies, so
79
+ tests outside an Actor's `dockerContextDir` (e.g. a root `test/` with standalone Actors) need to be listed in its
78
80
  `overrideActorContext`; `get-affected-actors` logs a warning for changed test files outside every Actor's context.
79
81
  6. **README classification** — a `README.md` file is `cosmetic` (only triggers a release build, not tests) if it
80
82
  lives inside the Actor's own `folder`; otherwise it's ignored entirely, since it isn't documentation for this
@@ -97,13 +99,12 @@ npx apify-test-tools get-affected-actors --base origin/master --since <sha>
97
99
  npx apify-test-tools get-affected-actors --since <sha>
98
100
  ```
99
101
 
100
- | Flag | Default | Meaning |
101
- | ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
102
- | `--base` | | Only what `HEAD` changes compared to this branch counts, like `git diff <base>...HEAD` |
103
- | `--since` | | Only changes after this commit count (see below) |
104
- | `--test-files-glob` | | Test files (glob from the repo root) that count as changes even when `.dockerignore` lists them, see [Change detection](#change-detection) |
105
- | `--actors` | all | Only report these Actors |
106
- | `--ignore` | none | Never report these Actors |
102
+ | Flag | Default | Meaning |
103
+ | ---------- | ------- | -------------------------------------------------------------------------------------- |
104
+ | `--base` | | Only what `HEAD` changes compared to this branch counts, like `git diff <base>...HEAD` |
105
+ | `--since` | | Only changes after this commit count (see below) |
106
+ | `--actors` | all | Only report these Actors |
107
+ | `--ignore` | none | Never report these Actors |
107
108
 
108
109
  Pass `--base`, `--since`, or both:
109
110
 
@@ -133,26 +134,33 @@ The output is a JSON document:
133
134
  "changedFiles": ["src/main.ts", "CHANGELOG.md"],
134
135
  "changelog": "Lines added to CHANGELOG.md, or null",
135
136
  "functionallyChangedActors": [{ "actorFullName": "john.doe/my-actor" }],
136
- "cosmeticallyChangedActors": [{ "actorFullName": "john.doe/other-actor" }]
137
+ "cosmeticallyChangedActors": [{ "actorFullName": "john.doe/other-actor" }],
138
+ "testChangedActors": [{ "actorFullName": "john.doe/my-actor" }]
137
139
  }
138
140
  ```
139
141
 
140
- Each changed Actor is in exactly one of the lists. `functionallyChangedActors` have at least one functional change,
141
- so they need a new build and tests. `cosmeticallyChangedActors` only have cosmetic changes (see
142
- [Change detection](#change-detection)), so they need a new build only when released: the PR workflow builds
143
- `functionallyChangedActors`, the release workflow builds both.
142
+ `functionallyChangedActors` have at least one functional change, so they need a new build and tests.
143
+ `cosmeticallyChangedActors` have cosmetic changes and no functional one (see [Change detection](#change-detection)), so
144
+ they need a new build only when released. An Actor is in at most one of these two lists. `testChangedActors` have a
145
+ changed platform test (`platformTestsGlob` in the config) in their context, whatever else changed, so their tests need
146
+ to run. A test change alone needs no release. The PR workflow builds `functionallyChangedActors` and
147
+ `testChangedActors`. The release workflow builds `functionallyChangedActors` and `cosmeticallyChangedActors`.
148
+
149
+ With `--base` and `--since`, an Actor is in `testChangedActors` when its tests changed on the branch and again since
150
+ `--since`, the same rule as for functional changes.
144
151
 
145
152
  ## Building Actors (`build`)
146
153
 
147
154
  `build` builds exactly the Actors it is given on stdin, from the checked-out branch as it is on the remote, and
148
- prints the builds as the same JSON array `build-from-local` prints. A detached HEAD is an error, since there is no
149
- branch to build from. It doesn't look at git history: which Actors to build is up to you, usually picked from the
155
+ prints the builds as the same JSON array `build-from-local` prints. An Actor listed twice is built once, so the lists
156
+ of `get-affected-actors` can be joined without removing duplicates first. A detached HEAD is an error, since there is
157
+ no branch to build from. It doesn't look at git history: which Actors to build is up to you, usually picked from the
150
158
  output of [`get-affected-actors`](#finding-affected-actors-get-affected-actors).
151
159
 
152
160
  ```bash
153
- # A PR: the Actors a branch changed functionally, built into the test version
161
+ # A PR: the Actors whose code or platform tests the branch changed, built into the test version
154
162
  npx apify-test-tools get-affected-actors --base origin/master \
155
- | jq '{ actors: .functionallyChangedActors }' \
163
+ | jq '{ actors: (.functionallyChangedActors + .testChangedActors) }' \
156
164
  | npx apify-test-tools build --version-number 0.99
157
165
 
158
166
  # A release: every Actor changed since the last release, into the version users run
@@ -285,7 +293,8 @@ the repository locked) on the Actors it is given on stdin, and prints a report o
285
293
  ... | npx apify-test-tools build --version-number 0.99 | npx apify-test-tools test > test-report.json
286
294
 
287
295
  # Scheduled tests: every Actor in the config, on its default build
288
- npx apify-test-tools get-actor-configs | npx apify-test-tools test test/platform/core > test-report.json
296
+ npx apify-test-tools get-actor-configs \
297
+ | npx apify-test-tools test --test-files-glob 'test/platform/{hourly,hourly-without-pr}/**' > test-report.json
289
298
 
290
299
  # Only some of them
291
300
  npx apify-test-tools get-actor-configs --actors john.doe/my-actor | npx apify-test-tools test > test-report.json
@@ -309,11 +318,19 @@ annotate the failing tests in a GitHub Actions run. It takes no other vitest opt
309
318
  test by name, run vitest directly with the builds in `ACTOR_BUILDS` (see
310
319
  [Running tests locally](lib.md#running-tests-locally)).
311
320
 
312
- | Argument / flag | Default | Meaning |
313
- | ---------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------- |
314
- | `[filters..]` | `test/platform` | Test files to run, as vitest filters |
315
- | `--vitest-output-file` | | Also keep vitest's own JSON report there, e.g. to upload it |
316
- | `--reporter` | | [Vitest reporter](https://vitest.dev/guide/reporters) to add to `default` and `json`, e.g. `github-actions`. Repeat it for more |
321
+ | Flag | Default | Meaning |
322
+ | ---------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
323
+ | `--test-files-glob` | `test/platform/**` | Glob from the repo root of the test files to run, see below |
324
+ | `--vitest-output-file` | | Also keep vitest's own JSON report there, e.g. to upload it |
325
+ | `--reporter` | | [Vitest reporter](https://vitest.dev/guide/reporters) to add to `default` and `json`, e.g. `github-actions`. Repeat it for more |
326
+
327
+ `test` takes no test paths as arguments, only `--test-files-glob`. The glob is read the same way as
328
+ `platformTestsGlob` in the config file. Vitest filters are parts of a file path, not globs, so `test` finds the files
329
+ the glob matches and passes vitest as few paths as it can: each folder whose files all match, with a trailing `/`, and
330
+ the other matched files one by one. `test/platform/{daily,hourly}/**` becomes two folders however many tests they
331
+ hold, so the command line stays short. Vitest still applies its own `include` and `exclude`, so helper files in the
332
+ glob run nothing. A glob that matches no file is an error, because vitest without filters would run every test of the
333
+ repo. Quote the glob, so the shell doesn't expand it.
317
334
 
318
335
  Vitest's own output goes to stderr, followed by a summary that lists every test with the link to its run. Stdout
319
336
  gets only the report:
@@ -432,15 +449,15 @@ To build from git, like CI does:
432
449
  5. Run tests against those builds. You can change tests and run on the same builds.
433
450
 
434
451
  ```bash
435
- npx apify-test-tools get-affected-actors --base origin/master --test-files-glob 'test/platform/**' \
436
- | jq '{ actors: .functionallyChangedActors }' \
452
+ npx apify-test-tools get-affected-actors --base origin/master \
453
+ | jq '{ actors: (.functionallyChangedActors + .testChangedActors) }' \
437
454
  | APIFY_TOKEN_JOHN_DOE=apify_api_xxx npx apify-test-tools build --version-number 0.99 > builds.json
438
455
  ```
439
456
 
440
457
  Run it with your dummy branch checked out: it builds the Actors that branch changed functionally, from the branch as
441
458
  it is on the remote, into the test version `0.99`. Add `--dry-run` to `build` to print the plan (the version and Git
442
459
  URL each Actor would be built from) first, and update `--base` to the branch you compare against. Tests only run for
443
- Actors in `ACTOR_BUILDS`, so pass `--test-files-glob` to count test changes even when `.dockerignore` lists them.
460
+ Actors in `ACTOR_BUILDS`, so the `jq` filter also builds the Actors whose platform tests changed.
444
461
 
445
462
  If you want to test against existing builds instead, skip the build and write the build array by hand, only for the
446
463
  Actors you need to test.
package/docs/workflows.md CHANGED
@@ -39,11 +39,12 @@ Validates a pull request. It runs two jobs in parallel.
39
39
  **`platformTest`** builds and tests the Actors the PR changes:
40
40
 
41
41
  1. Finds the affected Actors with [`get-affected-actors`](cli.md#finding-affected-actors-get-affected-actors),
42
- comparing the PR branch with its base branch. Changed files under `test/platform/**` count even when
43
- `.dockerignore` lists them.
44
- 2. Builds the functionally changed Actors into the test version `0.99` with [`build`](cli.md#building-actors-build).
45
- Cosmetic-only changes (README, CHANGELOG, schema formatting) need no tests, so they're not built.
46
- 3. Runs [`test`](cli.md#running-tests-test) on `./test/platform/<test-files-glob>` with those builds, so only the
42
+ comparing the PR branch with its base branch. Changed files that match `platformTestsGlob` in the config file
43
+ (default `test/platform/**`) are test changes, also when `.dockerignore` lists them.
44
+ 2. Builds the Actors with functional or test changes into the test version `0.99` with
45
+ [`build`](cli.md#building-actors-build). Cosmetic-only changes (README, CHANGELOG, schema formatting) need no
46
+ tests, so they're not built.
47
+ 3. Runs [`test`](cli.md#running-tests-test) on the files that match `test-files-glob` with those builds, so only the
47
48
  tests of the built Actors run. Failing tests are annotated on the PR.
48
49
  4. On success, caches the tested commit as the "last validated commit". The next push to the PR then only builds
49
50
  and tests the Actors that changed since it (see `--since` in
@@ -62,11 +63,11 @@ Validates a pull request. It runs two jobs in parallel.
62
63
  In a repo whose Actors aren't written in JavaScript or TypeScript, define the `build-check`, `lint` and `test`
63
64
  scripts, e.g. to run your language's own checks: without them, the TypeScript, Lint and Test checks fail.
64
65
 
65
- | Input | Type | Default | Description |
66
- | -------------------------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------ |
67
- | `skip-platform-tests` | boolean | `false` | Skip the `platformTest` job and run the static checks only |
68
- | `test-files-glob` | string | `''` | Which tests to run, relative to `test/platform`. Empty runs everything in it |
69
- | `require-env-vars-in-sync` | boolean | `true` | Fail the build unless the env var names of `0.99` match the default version's, see [Env var check](cli.md#env-var-check) |
66
+ | Input | Type | Default | Description |
67
+ | -------------------------- | ------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------- |
68
+ | `skip-platform-tests` | boolean | `false` | Skip the `platformTest` job and run the static checks only |
69
+ | `test-files-glob` | string | `test/platform/**` | Glob of the test files to run, from the repo root. Empty means the default. See [Splitting the tests](#splitting-the-tests) |
70
+ | `require-env-vars-in-sync` | boolean | `true` | Fail the build unless the env var names of `0.99` match the default version's, see [Env var check](cli.md#env-var-check) |
70
71
 
71
72
  Secrets: `TESTER_APIFY_TOKEN`, the Actor tokens in `CONFIG_SECRETS`, and optionally `NPM_TOKEN`. With
72
73
  `skip-platform-tests`, only `NPM_TOKEN`.
@@ -83,6 +84,9 @@ on:
83
84
  jobs:
84
85
  buildDevelAndTest:
85
86
  uses: apify/apify-test-tools/.github/workflows/public_pr-build-test.yaml@workflows-v0
87
+ with:
88
+ # Leaves out the *-without-pr folders, see "Splitting the tests"
89
+ test-files-glob: 'test/platform/{daily,hourly}/**'
86
90
  secrets:
87
91
  TESTER_APIFY_TOKEN: ${{ secrets.TESTER_APIFY_TOKEN }}
88
92
  # One entry per secret apify-test-tools.config.json names, today each `tokenEnvVar`
@@ -101,7 +105,7 @@ Releases the Actors a push changed. Skipped when the head commit message contain
101
105
  1. `get-affected-actors --since ${{ github.event.before }}` finds everything the push changed. See
102
106
  [Releasing Actors](cli.md#releasing-actors) for how the released range is determined.
103
107
  2. `build --version-number default` builds both the functionally and the cosmetically changed Actors into their
104
- default version, the one users run.
108
+ default version, the one users run. A push that only changes platform tests builds nothing.
105
109
  3. [`notify`](cli.md#notifying-notify) posts the release (its changelog additions and its commits, linked to
106
110
  GitHub) to the report channel. It posts nothing when the push brought no new commits.
107
111
  4. `delete-old-builds` deletes old builds of every Actor in the config (see
@@ -146,15 +150,15 @@ Runs the platform tests of every Actor in the config against its current default
146
150
  reaches the tests. Both the report of `test` and
147
151
  vitest's own JSON report are uploaded as the `vitest-results` artifact. Runs only on `master` or `main`.
148
152
 
149
- | Input | Type | Default | Description |
150
- | ---------------------- | ------ | ------- | ---------------------------------------------------------------------------- |
151
- | `test-files-glob` | string | `''` | Which tests to run, relative to `test/platform`. Empty runs everything in it |
152
- | `subtest` | string | `''` | Deprecated, use `test-files-glob` |
153
- | `report-slack-channel` | string | `''` | Slack channel for the results. Empty skips Slack |
153
+ | Input | Type | Default | Description |
154
+ | ---------------------- | ------ | ------------------ | --------------------------------------------------------------------------------------------------------------------------- |
155
+ | `test-files-glob` | string | `test/platform/**` | Glob of the test files to run, from the repo root. Empty means the default. See [Splitting the tests](#splitting-the-tests) |
156
+ | `report-slack-channel` | string | `''` | Slack channel for the results. Empty skips Slack |
154
157
 
155
158
  Secrets: `TESTER_APIFY_TOKEN` (required), `SLACK_BOT_TOKEN`, and optionally `NPM_TOKEN`.
156
159
 
157
- How often to run which tests is up to you. For example, every test once a day:
160
+ How often to run which tests is up to you, see [Splitting the tests](#splitting-the-tests). For example, the daily
161
+ tests once a day:
158
162
 
159
163
  ```yaml
160
164
  name: Platform tests - Daily
@@ -169,13 +173,14 @@ jobs:
169
173
  platformTestsDaily:
170
174
  uses: apify/apify-test-tools/.github/workflows/public_platform-tests.yaml@workflows-v0
171
175
  with:
176
+ test-files-glob: 'test/platform/{daily,daily-without-pr}/**'
172
177
  report-slack-channel: '#notif-my-repo'
173
178
  secrets:
174
179
  TESTER_APIFY_TOKEN: ${{ secrets.TESTER_APIFY_TOKEN }}
175
180
  SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
176
181
  ```
177
182
 
178
- and a few critical tests, kept in e.g. `test/platform/hourly`, every hour:
183
+ and a few critical tests every hour:
179
184
 
180
185
  ```yaml
181
186
  name: Platform tests - Hourly
@@ -190,13 +195,41 @@ jobs:
190
195
  platformTestsHourly:
191
196
  uses: apify/apify-test-tools/.github/workflows/public_platform-tests.yaml@workflows-v0
192
197
  with:
193
- test-files-glob: hourly
198
+ test-files-glob: 'test/platform/{hourly,hourly-without-pr}/**'
194
199
  report-slack-channel: '#notif-my-repo'
195
200
  secrets:
196
201
  TESTER_APIFY_TOKEN: ${{ secrets.TESTER_APIFY_TOKEN }}
197
202
  SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
198
203
  ```
199
204
 
205
+ ## Splitting the tests
206
+
207
+ `test-files-glob` is one glob from the repo root, for example `test/platform/**`. Both test workflows take it, and it
208
+ only selects which tests run. Which changed files count as test changes is a separate setting, `platformTestsGlob` in
209
+ the config file (default `test/platform/**`). So on a PR, a change to a `*-without-pr` test or to a shared helper
210
+ still builds and tests the Actors, even though the PR doesn't run that test.
211
+
212
+ Most repos split `test/platform` into folders by how often the tests run, and by whether a PR runs them:
213
+
214
+ | Folder | Runs |
215
+ | --------------------------------- | --------------------------------------------------------------- |
216
+ | `test/platform/hourly` | every hour and on every PR |
217
+ | `test/platform/daily` | once a day and on every PR |
218
+ | `test/platform/hourly-without-pr` | every hour only, e.g. tests that are too slow or costly for PRs |
219
+ | `test/platform/daily-without-pr` | once a day only |
220
+
221
+ Each workflow then selects its folders with a brace pattern:
222
+
223
+ | Job | `test-files-glob` |
224
+ | ------------------------------------- | --------------------------------------------- |
225
+ | PR (`public_pr-build-test.yaml`) | `test/platform/{daily,hourly}/**` |
226
+ | Hourly (`public_platform-tests.yaml`) | `test/platform/{hourly,hourly-without-pr}/**` |
227
+ | Daily (`public_platform-tests.yaml`) | `test/platform/{daily,daily-without-pr}/**` |
228
+
229
+ Write the glob to match files, not folders: `test/platform/hourly` matches no file, `test/platform/hourly/**` matches
230
+ every file in it. A glob that matches no file fails the test step, so a typo doesn't pass as a run with no tests.
231
+ `test` only runs the matched files that vitest sees as test files, so a helper in the glob runs nothing.
232
+
200
233
  ## `public_review.yaml`
201
234
 
202
235
  Optional. Reviews a PR against the shared guidelines when you add the trigger label, and again on every push while
@@ -290,7 +323,7 @@ jobs:
290
323
  platformTestsHourly:
291
324
  uses: apify/apify-test-tools/.github/workflows/public_platform-tests.yaml@workflows-v0
292
325
  with:
293
- test-files-glob: hourly
326
+ test-files-glob: 'test/platform/{hourly,hourly-without-pr}/**'
294
327
  report-slack-channel: '#notif-my-repo'
295
328
  secrets:
296
329
  TESTER_APIFY_TOKEN: ${{ secrets.TESTER_APIFY_TOKEN }}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apify-test-tools",
3
- "version": "0.10.2-beta.19",
3
+ "version": "0.10.2-beta.20",
4
4
  "type": "module",
5
5
  "description": "TBD",
6
6
  "repository": {
@@ -91,7 +91,7 @@ describe('CLI bundle', () => {
91
91
  for (const command of [
92
92
  'get-actor-configs',
93
93
  'get-affected-actors',
94
- 'test [filters..]',
94
+ 'test',
95
95
  'notify',
96
96
  'build',
97
97
  'build-from-local',
@@ -165,11 +165,15 @@ describe('sandbox dummies', () => {${actorTests}
165
165
  `,
166
166
  );
167
167
 
168
- const tested = await runProcess(process.execPath, [bundlePath, 'test', 'test/platform'], {
169
- cwd: repoDir,
170
- env: { ...cliEnv, TESTER_APIFY_TOKEN: token! },
171
- input: JSON.stringify(builds),
172
- });
168
+ const tested = await runProcess(
169
+ process.execPath,
170
+ [bundlePath, 'test', '--test-files-glob', 'test/platform/**'],
171
+ {
172
+ cwd: repoDir,
173
+ env: { ...cliEnv, TESTER_APIFY_TOKEN: token! },
174
+ input: JSON.stringify(builds),
175
+ },
176
+ );
173
177
  expect(tested.status, tested.stderr).toBe(0);
174
178
  expect(tested.stderr).toContain('https://console.apify.com/view/runs/');
175
179
 
@@ -36,6 +36,14 @@ describe('selectActors', () => {
36
36
  ]);
37
37
  });
38
38
 
39
+ it('selects an actor listed twice once, in config order', () => {
40
+ // build relies on this to build an Actor in both functionallyChangedActors and testChangedActors once
41
+ expect(names(selectActors({ actors: ['owner/c', 'owner/a', 'owner/c'], ignore: [] }, configs))).toStrictEqual([
42
+ 'owner/a',
43
+ 'owner/c',
44
+ ]);
45
+ });
46
+
39
47
  it('drops the actors listed in --ignore', () => {
40
48
  expect(names(selectActors({ actors: [], ignore: ['owner/b'] }, configs))).toStrictEqual(['owner/a', 'owner/c']);
41
49
  });
@@ -72,10 +72,31 @@ const runGetAffectedActors = (...args: string[]) => {
72
72
  return JSON.parse(result.stdout);
73
73
  };
74
74
 
75
+ const names = (actors: { actorFullName: string }[]) =>
76
+ actors.map(({ actorFullName }) => actorFullName.replace('owner/actor-', '')).sort();
77
+
78
+ /** All three lists, for the tests about test changes */
79
+ const getAllChangedActors = (...args: string[]) => {
80
+ const document = runGetAffectedActors(...args);
81
+ return {
82
+ functional: names(document.functionallyChangedActors),
83
+ cosmetic: names(document.cosmeticallyChangedActors),
84
+ test: names(document.testChangedActors),
85
+ };
86
+ };
87
+
88
+ /** Writes the config with actor-a's context also covering `extraContext`. It is committed with the next commit. */
89
+ const writeConfigWithTestsInContext = async (extraContext: string, settings: Record<string, unknown> = {}) => {
90
+ const actors = CONFIG.actors.map((actor) =>
91
+ actor.folder === 'actors/a'
92
+ ? { ...actor, overrideActorContext: [...actor.overrideActorContext!, extraContext] }
93
+ : actor,
94
+ );
95
+ await writeFile(path.join(repoDir, 'apify-test-tools.config.json'), JSON.stringify({ ...settings, actors }));
96
+ };
97
+
75
98
  const getAffectedActors = (...args: string[]) => {
76
99
  const document = runGetAffectedActors(...args);
77
- const names = (actors: { actorFullName: string }[]) =>
78
- actors.map(({ actorFullName }) => actorFullName.replace('owner/actor-', '')).sort();
79
100
  return {
80
101
  functional: names(document.functionallyChangedActors),
81
102
  cosmetic: names(document.cosmeticallyChangedActors),
@@ -191,8 +212,57 @@ describe('get-affected-actors CLI on real git histories', () => {
191
212
  expect(getAffectedActors('--base', 'main', '--since', 'e'.repeat(40))).toStrictEqual(wholeBranch);
192
213
  });
193
214
 
215
+ it('lists Actors whose platform tests changed, and a test change alone is not functional', async () => {
216
+ await writeConfigWithTestsInContext('test');
217
+ await writeFiles('test/', ['.dockerignore']);
218
+ // A test and its helper, both under the default platformTestsGlob test/platform/**
219
+ await commit('test(a): change a test and its helper', 'test/platform/a.test.ts', 'test/platform/utils.ts');
220
+
221
+ const testOnly = { functional: [], cosmetic: [], test: ['a'] };
222
+ // A PR runs the tests
223
+ expect(getAllChangedActors('--base', 'main')).toStrictEqual(testOnly);
224
+ // A release builds only the functional and cosmetic lists, so it builds nothing
225
+ expect(getAllChangedActors('--since', git('rev-parse', 'main'))).toStrictEqual(testOnly);
226
+ });
227
+
228
+ it('lists an Actor in both lists when its source and its tests changed', async () => {
229
+ await writeConfigWithTestsInContext('test');
230
+ await commit('feat(a): change and test it', 'actors/a/main.ts', 'test/platform/a.test.ts');
231
+
232
+ expect(getAllChangedActors('--base', 'main')).toStrictEqual({ functional: ['a'], cosmetic: [], test: ['a'] });
233
+ });
234
+
235
+ it('lists only the tests changed since the last validated commit', async () => {
236
+ await writeConfigWithTestsInContext('test');
237
+ const validated = await commit('feat(a): validated change', 'actors/a/main.ts');
238
+ await commit('test(a): new test', 'test/platform/a.test.ts');
239
+
240
+ // The validated build of A has the branch's code, only the tests changed since
241
+ expect(getAllChangedActors('--base', 'main', '--since', validated)).toStrictEqual({
242
+ functional: [],
243
+ cosmetic: [],
244
+ test: ['a'],
245
+ });
246
+ });
247
+
248
+ it('reads which files are platform tests from platformTestsGlob in the config', async () => {
249
+ await writeConfigWithTestsInContext('e2e', { platformTestsGlob: 'e2e/platform/**' });
250
+ await commit('test(a): new test', 'e2e/platform/a.test.ts');
251
+ expect(getAllChangedActors('--base', 'main')).toStrictEqual({ functional: [], cosmetic: [], test: ['a'] });
252
+
253
+ // Outside the glob, a file in the context is a source change like any other
254
+ await commit('feat(a): other file', 'e2e/fixtures/data.json');
255
+ expect(getAllChangedActors('--base', 'main')).toStrictEqual({ functional: ['a'], cosmetic: [], test: ['a'] });
256
+ });
257
+
194
258
  it('reports nothing for an empty range', async () => {
195
- const nothing = { commits: [], changedFiles: [], functionallyChangedActors: [], cosmeticallyChangedActors: [] };
259
+ const nothing = {
260
+ commits: [],
261
+ changedFiles: [],
262
+ functionallyChangedActors: [],
263
+ cosmeticallyChangedActors: [],
264
+ testChangedActors: [],
265
+ };
196
266
  // The branch has no commits of its own yet
197
267
  expect(runGetAffectedActors('--base', 'main')).toMatchObject(nothing);
198
268
  // A release with nothing new since the last one
@@ -24,9 +24,10 @@ describe('getAffectedActors', () => {
24
24
 
25
25
  beforeEach(() => {
26
26
  vi.spyOn(LoadConfig, 'readConfigFile').mockResolvedValue([actorA, actorB]);
27
+ vi.spyOn(LoadConfig, 'readRepoSettings').mockResolvedValue({ platformTestsGlob: 'test/platform/**' });
27
28
  changedActorsSpy = vi
28
29
  .spyOn(DiffChanges, 'getChangedActors')
29
- .mockResolvedValue({ functional: [actorA], cosmetic: [actorB] });
30
+ .mockResolvedValue({ functional: [actorA], cosmetic: [actorB], test: [] });
30
31
  vi.spyOn(Git, 'resolveCommitSha').mockResolvedValue(headSha);
31
32
  vi.spyOn(Git, 'getChangedFilesBetween').mockResolvedValue(changedFiles);
32
33
  vi.spyOn(Git, 'getChangelogAdditions').mockResolvedValue('- Added foo');
@@ -62,6 +63,7 @@ describe('getAffectedActors', () => {
62
63
  // Only the names, the config file stays the source of truth for the rest
63
64
  functionallyChangedActors: [{ actorFullName: 'owner/a' }],
64
65
  cosmeticallyChangedActors: [{ actorFullName: 'owner/b' }],
66
+ testChangedActors: [],
65
67
  });
66
68
  expect(Git.getMergeBase).toHaveBeenCalledWith('origin/master', 'HEAD');
67
69
  expect(Git.getChangedFilesBetween).toHaveBeenCalledWith(mergeBaseSha, headSha);
@@ -71,6 +73,7 @@ describe('getAffectedActors', () => {
71
73
  filepathsChanged: changedFiles,
72
74
  actorConfigs: [actorA, actorB],
73
75
  range: { fromRef: mergeBaseSha, toRef: headSha },
76
+ platformTestsGlob: 'test/platform/**',
74
77
  });
75
78
  expect(findCommitSpy).not.toHaveBeenCalled();
76
79
  });
@@ -96,15 +99,20 @@ describe('getAffectedActors', () => {
96
99
  return {
97
100
  functional: result.functionallyChangedActors.map(({ actorFullName }) => actorFullName),
98
101
  cosmetic: result.cosmeticallyChangedActors.map(({ actorFullName }) => actorFullName),
102
+ test: result.testChangedActors.map(({ actorFullName }) => actorFullName),
99
103
  };
100
104
  };
101
105
 
102
106
  it('classifies the changes since --since separately', async () => {
103
- await getNarrowed({ functional: [actorA], cosmetic: [] }, { functional: [actorA], cosmetic: [] });
107
+ await getNarrowed(
108
+ { functional: [actorA], cosmetic: [], test: [] },
109
+ { functional: [actorA], cosmetic: [], test: [] },
110
+ );
104
111
  expect(changedActorsSpy).toHaveBeenCalledWith({
105
112
  filepathsChanged: sinceFiles,
106
113
  actorConfigs: [actorA, actorB],
107
114
  range: { fromRef: sinceSha, toRef: headSha },
115
+ platformTestsGlob: 'test/platform/**',
108
116
  });
109
117
  });
110
118
 
@@ -116,25 +124,55 @@ describe('getAffectedActors', () => {
116
124
 
117
125
  it('retests an Actor whose validated change meets a new functional change', async () => {
118
126
  await expect(
119
- getNarrowed({ functional: [actorA], cosmetic: [] }, { functional: [actorA], cosmetic: [] }),
120
- ).resolves.toStrictEqual({ functional: ['owner/a'], cosmetic: [] });
127
+ getNarrowed(
128
+ { functional: [actorA], cosmetic: [], test: [] },
129
+ { functional: [actorA], cosmetic: [], test: [] },
130
+ ),
131
+ ).resolves.toStrictEqual({ functional: ['owner/a'], cosmetic: [], test: [] });
121
132
  });
122
133
 
123
134
  it('leaves out Actors that only the branch changed before --since or only --base changed', async () => {
124
135
  await expect(
125
- getNarrowed({ functional: [actorA], cosmetic: [] }, { functional: [actorB], cosmetic: [] }),
126
- ).resolves.toStrictEqual({ functional: [], cosmetic: [] });
136
+ getNarrowed(
137
+ { functional: [actorA], cosmetic: [], test: [] },
138
+ { functional: [actorB], cosmetic: [], test: [] },
139
+ ),
140
+ ).resolves.toStrictEqual({ functional: [], cosmetic: [], test: [] });
127
141
  });
128
142
 
129
143
  it('takes the less impactful change when the two disagree', async () => {
130
144
  // A validated functional change, then only cosmetic changes (e.g. a README merged in from base)
131
145
  await expect(
132
- getNarrowed({ functional: [actorA], cosmetic: [] }, { functional: [], cosmetic: [actorA] }),
133
- ).resolves.toStrictEqual({ functional: [], cosmetic: ['owner/a'] });
146
+ getNarrowed(
147
+ { functional: [actorA], cosmetic: [], test: [] },
148
+ { functional: [], cosmetic: [actorA], test: [] },
149
+ ),
150
+ ).resolves.toStrictEqual({ functional: [], cosmetic: ['owner/a'], test: [] });
134
151
  // A functional change merged in from base, while the branch only changes the README
135
152
  await expect(
136
- getNarrowed({ functional: [], cosmetic: [actorA] }, { functional: [actorA], cosmetic: [] }),
137
- ).resolves.toStrictEqual({ functional: [], cosmetic: ['owner/a'] });
153
+ getNarrowed(
154
+ { functional: [], cosmetic: [actorA], test: [] },
155
+ { functional: [actorA], cosmetic: [], test: [] },
156
+ ),
157
+ ).resolves.toStrictEqual({ functional: [], cosmetic: ['owner/a'], test: [] });
158
+ });
159
+
160
+ it('runs the tests of an Actor whose tests the branch changed and changed again since --since', async () => {
161
+ await expect(
162
+ getNarrowed(
163
+ { functional: [actorA], cosmetic: [], test: [actorA, actorB] },
164
+ { functional: [], cosmetic: [], test: [actorA] },
165
+ ),
166
+ ).resolves.toStrictEqual({ functional: [], cosmetic: [], test: ['owner/a'] });
167
+ });
168
+
169
+ it('leaves out test changes that only --base or only the validated part of the branch has', async () => {
170
+ await expect(
171
+ getNarrowed(
172
+ { functional: [], cosmetic: [], test: [actorA] },
173
+ { functional: [], cosmetic: [], test: [actorB] },
174
+ ),
175
+ ).resolves.toStrictEqual({ functional: [], cosmetic: [], test: [] });
138
176
  });
139
177
 
140
178
  it.each([
@@ -172,6 +210,7 @@ describe('getAffectedActors', () => {
172
210
  // Only the names, the config file stays the source of truth for the rest
173
211
  functionallyChangedActors: [{ actorFullName: 'owner/a' }],
174
212
  cosmeticallyChangedActors: [{ actorFullName: 'owner/b' }],
213
+ testChangedActors: [],
175
214
  });
176
215
  expect(resolveBaseSpy).toHaveBeenCalledWith('HEAD~2');
177
216
  expect(commitsBetweenSpy).toHaveBeenCalledWith(sinceSha, headSha);