apify-test-tools 0.9.1-beta.6 → 0.9.1-beta.7

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.
@@ -0,0 +1,197 @@
1
+ name: Test
2
+
3
+ on:
4
+ workflow_call:
5
+ inputs:
6
+ working-directory:
7
+ type: string
8
+ default: .
9
+ required: false
10
+ additional-working-directory:
11
+ type: string
12
+ required: false
13
+ skip-platform-tests:
14
+ type: boolean
15
+ default: false
16
+ required: false
17
+ # Specify what tests you want to run, relative to `test/platform`
18
+ test-files-glob:
19
+ type: string
20
+ default: ''
21
+ required: false
22
+
23
+ concurrency:
24
+ group: ${{ github.workflow }}-${{ github.ref }}
25
+ cancel-in-progress: true
26
+
27
+ jobs:
28
+ platformTest:
29
+ if: ${{ !inputs.skip-platform-tests }}
30
+ runs-on: ubuntu-latest
31
+ defaults:
32
+ run:
33
+ working-directory: ${{ inputs.working-directory }}
34
+ steps:
35
+ - name: Setup repository and dependencies
36
+ id: setup
37
+ # For testing, you have to temporarily change the branch
38
+ uses: apify/apify-test-tools/.github/actions/checkout-restore-dependencies@v0
39
+ with:
40
+ working-directory: ${{ inputs.working-directory }}
41
+ additional-working-directory: ${{ inputs.additional-working-directory }}
42
+ # Some repos use private npm packages, they need to pass NPM_TOKEN secret to this workflow
43
+ npm-token: ${{ secrets.NPM_TOKEN }}
44
+
45
+ - name: Pick last validated commit from cache
46
+ id: base_commit
47
+ uses: actions/cache@v5
48
+ with:
49
+ # We have to call it base commit for the library but it makes more sense as "last validated commit"
50
+ # Using run_id ensures the key is always unique so the cache always saves at end-of-job (no "cache hit, not saving" issue)
51
+ path: ./base_commit.txt
52
+ key: base_commit-${{ github.run_id }}
53
+ # This is needed to force overriding the cache with new entry at the end
54
+ restore-keys: base_commit-
55
+
56
+ # run-with-apify-tokens.mjs reads the token names from in apify-test-tools.config.json
57
+ # and hands the build those secrets and nothing else, so the tokens stay in this one command instead of the whole job.
58
+ # The library never sees secrets it doesn't need
59
+ - name: Build
60
+ id: build
61
+ env:
62
+ ALL_SECRETS: ${{ toJSON(secrets) }}
63
+ # Branch names are attacker-controlled on fork PRs, so they reach the script as
64
+ # environment variables rather than being interpolated into it.
65
+ HEAD_REF: ${{ github.head_ref }}
66
+ BASE_REF: ${{ github.base_ref }}
67
+ SCRIPTS_PATH: ${{ steps.setup.outputs.scripts-path }}
68
+ run: |
69
+ base_commit=$(cat base_commit.txt 2>/dev/null || echo "")
70
+ echo "Last validated (base) commit to be used for build & test: $base_commit"
71
+ args=(
72
+ --source-branch "origin/$HEAD_REF"
73
+ --target-branch "origin/$BASE_REF"
74
+ --use-docker-cache
75
+ )
76
+ [ -n "$base_commit" ] && args+=(--base-commit "$base_commit")
77
+ actor_builds=$(node "$SCRIPTS_PATH/run-with-apify-tokens.mjs" \
78
+ npx apify-test-tools build "${args[@]}")
79
+ echo "actor_builds=$actor_builds" | tee -a "$GITHUB_OUTPUT"
80
+
81
+ # Test files are outside every actor's Docker build context, so the diff above finds nothing
82
+ # to build and every test skips. Right for "rebuild?", wrong for "test?".
83
+ - name: Find changed platform tests
84
+ id: changed_tests
85
+ env:
86
+ # Branch names are attacker-controlled on fork PRs, so they reach the script as
87
+ # environment variables rather than being interpolated into it.
88
+ HEAD_REF: ${{ github.head_ref }}
89
+ BASE_REF: ${{ github.base_ref }}
90
+ WORKING_DIRECTORY: ${{ inputs.working-directory }}
91
+ run: |
92
+ work_dir="$WORKING_DIRECTORY"
93
+ [ "$work_dir" = "." ] && work_dir=""
94
+ prefix="${work_dir:+$work_dir/}test/platform/"
95
+ changed=$(git diff --name-only --diff-filter=d "origin/$BASE_REF...HEAD" -- "$prefix" \
96
+ | sed "s|^$prefix|./test/platform/|" | tr '\n' ' ')
97
+ branch_actors=$(npx apify-test-tools get-affected-actors \
98
+ --source-branch "origin/$HEAD_REF" \
99
+ --target-branch "origin/$BASE_REF")
100
+ if [ -n "$changed" ] && [ "$branch_actors" = "[]" ]; then
101
+ echo "target=$changed" | tee -a "$GITHUB_OUTPUT"
102
+ elif [ -n "$changed" ]; then
103
+ echo "Platform tests: the changed platform tests did not run. This branch also changes actor code validated at an earlier commit, so they could only have run against the deployed build, which lacks those changes. Re-run this job from scratch to rebuild and test together." >> "$GITHUB_STEP_SUMMARY"
104
+ fi
105
+
106
+ # Runs the repo's test code and its whole import graph, so it gets the tester token only.
107
+ - name: Test
108
+ env:
109
+ ACTOR_BUILDS: ${{ steps.build.outputs.actor_builds }}
110
+ TESTER_APIFY_TOKEN: ${{ secrets.TESTER_APIFY_TOKEN }}
111
+ # No build to pin to, so lift the per-actor gate and let each run use the deployed build.
112
+ RUN_ALL_PLATFORM_TESTS: ${{ steps.changed_tests.outputs.target && '1' || '' }}
113
+ # `related` runs the tests that import each changed file, so a changed helper runs its dependents.
114
+ # A plain filter would match no test for a helper and exit 1; `related` exits 0 when nothing imports it.
115
+ run: npx vitest ${{ steps.changed_tests.outputs.target && format('related {0}', steps.changed_tests.outputs.target) || format('./test/platform/{0}', inputs.test-files-glob) }} --run --maxConcurrency 20 --fileParallelism=true --maxWorkers 100
116
+
117
+ # NOTE: This is an optimization that if we did functional changes and later only cosmetic changes (e.g. dev readme), we will compare changes files only for the last commit. This must run after tests because we want to cache the latest commit only if the tests are successful
118
+ - name: Store last validated commit to cache
119
+ env:
120
+ HEAD_REF: ${{ github.head_ref }}
121
+ BASE_REF: ${{ github.base_ref }}
122
+ run: |
123
+ base_commit=$(cat base_commit.txt 2>/dev/null || echo "")
124
+ echo "Old last validated (base) commit: $base_commit"
125
+ args=(
126
+ --source-branch "origin/$HEAD_REF"
127
+ --target-branch "origin/$BASE_REF"
128
+ )
129
+ [ -n "$base_commit" ] && args+=(--base-commit "$base_commit")
130
+ npx apify-test-tools get-latest-commit "${args[@]}" > ./base_commit.txt
131
+ echo "New last validated (base) commit: $(cat base_commit.txt)"
132
+
133
+ # Pure static checks against the repo. Needs no Apify or Slack credentials, only the npm
134
+ # token to install dependencies.
135
+ unitTest:
136
+ # TODO: only run on changes to code
137
+ runs-on: ubuntu-latest
138
+ defaults:
139
+ run:
140
+ working-directory: ${{ inputs.working-directory }}
141
+
142
+ steps:
143
+ - name: Setup repository and dependencies
144
+ # For testing, you have to temporarily change the branch
145
+ uses: apify/apify-test-tools/.github/actions/checkout-restore-dependencies@v0
146
+ with:
147
+ working-directory: ${{ inputs.working-directory }}
148
+ additional-working-directory: ${{ inputs.additional-working-directory }}
149
+ npm-token: ${{ secrets.NPM_TOKEN }}
150
+
151
+ - name: TypeScript
152
+ # Some repos require custom build checks, so we check if the `build-check` script exists and run it if
153
+ # it does. Otherwise default to the standard `npx tsc --noEmit` check
154
+ run: |
155
+ HAS_CUSTOM_BUILD_CHECK=$(jq -r '.scripts["build-check"] // empty' package.json)
156
+ if [ -n "$HAS_CUSTOM_BUILD_CHECK" ]; then
157
+ echo "Custom build check script found. Running it.";
158
+ npm run build-check;
159
+ else
160
+ echo "No custom build check script found. Running standard tsc --noEmit.";
161
+ npx tsc --noEmit;
162
+ fi
163
+
164
+ - name: Lint
165
+ run: npm run lint
166
+
167
+ - name: Test
168
+ run: npm test
169
+
170
+ - name: Formatter Check
171
+ run: |
172
+ HAS_FORMAT_CHECK_SCRIPT=$(jq -r '.scripts["format:check"] // empty' package.json)
173
+ if [ -n "$HAS_FORMAT_CHECK_SCRIPT" ]; then
174
+ echo "Custom format:check script found. Running it.";
175
+ npm run format:check;
176
+ elif [ -d "node_modules/prettier" ]; then
177
+ echo "Prettier is installed. Running prettier --check .";
178
+ npx prettier --check .;
179
+ else
180
+ echo "Prettier is not installed. Skipping Prettier check.";
181
+ fi
182
+
183
+ - name: Unused Exports Check
184
+ run: |
185
+ HAS_CHECK_UNUSED_SCRIPT=$(jq -r '.scripts["check-unused"] // empty' package.json)
186
+ if [ -n "$HAS_CHECK_UNUSED_SCRIPT" ]; then
187
+ echo "Custom check-unused script found. Running it.";
188
+ npm run check-unused;
189
+ elif [ -d "node_modules/knip" ]; then
190
+ echo "Knip is installed. Running knip to check for unused exports.";
191
+ npx knip --include exports,types;
192
+ elif [ -d "node_modules/ts-unused-exports" ]; then
193
+ echo "Knip is not installed, but ts-unused-exports is. Running ts-unused-exports to check for unused exports.";
194
+ npx ts-unused-exports ./tsconfig.json;
195
+ else
196
+ echo "knip nor ts-unused-exports are installed. Skipping unused exports check.";
197
+ fi
@@ -0,0 +1,57 @@
1
+ name: Build latest and report to slack
2
+
3
+ on:
4
+ workflow_call:
5
+ inputs:
6
+ working-directory:
7
+ type: string
8
+ default: .
9
+ required: false
10
+ additional-working-directory:
11
+ type: string
12
+ required: false
13
+ # Defaults to #notif-<repo-name> when empty, matching the previous behavior.
14
+ report-slack-channel:
15
+ type: string
16
+ default: ''
17
+ required: false
18
+
19
+ jobs:
20
+ pushBuildLatest:
21
+ name: 'Build latest: ${{github.repository}} ${{github.event.head_commit.message}}'
22
+ if: |
23
+ !contains(github.event.head_commit.message, '[skip ci]') &&
24
+ !contains(github.event.head_commit.message, '[skip platform-test]')
25
+
26
+ runs-on: ubuntu-latest
27
+ defaults:
28
+ run:
29
+ working-directory: ${{ inputs.working-directory }}
30
+
31
+ steps:
32
+ - name: Setup repository and dependencies
33
+ id: setup
34
+ # For testing, you have to temporarily change the branch
35
+ uses: apify/apify-test-tools/.github/actions/checkout-restore-dependencies@v0
36
+ with:
37
+ working-directory: ${{ inputs.working-directory }}
38
+ additional-working-directory: ${{ inputs.additional-working-directory }}
39
+ npm-token: ${{ secrets.NPM_TOKEN }}
40
+
41
+ # run-with-apify-tokens.mjs reads the token names from in apify-test-tools.config.json
42
+ # and hands the build those secrets and nothing else, so the tokens stay in this one command instead of the whole job.
43
+ # The library never sees secrets it doesn't need
44
+ - name: Release Actors and notify
45
+ env:
46
+ ALL_SECRETS: ${{ toJSON(secrets) }}
47
+ SLACK_TOKEN_RELEASES_BOT: ${{ secrets.SLACK_TOKEN_RELEASES_BOT }}
48
+ run: |
49
+ node "${{ steps.setup.outputs.scripts-path }}/run-with-apify-tokens.mjs" \
50
+ npx apify-test-tools release --push-event-path ${{ github.event_path }} --release-slack-channel "#delivery-public-actors" --report-slack-channel "${{ inputs.report-slack-channel || format('#notif-{0}', github.event.repository.name) }}" --use-docker-cache
51
+
52
+ - name: Delete old builds
53
+ env:
54
+ ALL_SECRETS: ${{ toJSON(secrets) }}
55
+ run: |
56
+ node "${{ steps.setup.outputs.scripts-path }}/run-with-apify-tokens.mjs" \
57
+ npx apify-test-tools delete-old-builds
@@ -0,0 +1,114 @@
1
+ name: 'Claude Code Review'
2
+
3
+ on:
4
+ workflow_call:
5
+ inputs:
6
+ label:
7
+ default: Ask claude review
8
+ type: string
9
+ description: Label that triggers a review. Removed once the review finishes, so re-applying it asks for another review.
10
+ required: false
11
+ reviewed-label:
12
+ default: claude reviewed
13
+ type: string
14
+ description: Label added after a successful review and never removed. Set to an empty string to disable.
15
+ required: false
16
+ prompt-ref:
17
+ default: v0
18
+ type: string
19
+ description: >-
20
+ Ref of this repository to fetch the review instructions from. Defaults to the
21
+ major tag, matching the ref consumers call this workflow at. Override it only to
22
+ test a prompt change from a branch.
23
+ required: false
24
+ model:
25
+ default: sonnet
26
+ type: string
27
+ description: Model to use for Claude Code (best, sonnet, opus, haiku, sonnet[1m], opus[1m], opusplan)
28
+ required: false
29
+ working-directory:
30
+ type: string
31
+ default: .
32
+ required: false
33
+ additional-working-directory:
34
+ type: string
35
+ required: false
36
+ secrets:
37
+ ANTHROPIC_API_KEY:
38
+ required: true
39
+ description: Claude API key
40
+
41
+ jobs:
42
+ review:
43
+ if: |
44
+ (github.event.action == 'labeled' && github.event.label.name == inputs.label) ||
45
+ (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, inputs.label))
46
+ runs-on: ubuntu-latest
47
+ timeout-minutes: 7
48
+ concurrency:
49
+ group: 'claude-code-review-${{ github.event.pull_request.number }}'
50
+ cancel-in-progress: true
51
+ permissions:
52
+ contents: 'read'
53
+ id-token: 'write'
54
+ issues: 'write'
55
+ pull-requests: 'write'
56
+ steps:
57
+ - name: Checkout Repository
58
+ uses: actions/checkout@v5
59
+ with:
60
+ fetch-depth: 0
61
+ # We want to test our branch, not GitHub's fake merge commit (we must test that before merging anyway)
62
+ # head_ref must be used for pull_request but for push and schedule events we have to use ref to get the branch name
63
+ ref: ${{ github.head_ref || github.ref }}
64
+
65
+ - name: 'Fetch review instructions'
66
+ run: |
67
+ curl -sfL \
68
+ "https://raw.githubusercontent.com/apify/apify-test-tools/${{ inputs.prompt-ref }}/.github/review-prompt.md" \
69
+ -o "${{ github.workspace }}/.claude-review-prompt.md"
70
+
71
+ - name: 'Review PR against guidelines'
72
+ id: 'review'
73
+ uses: anthropics/claude-code-action@v1
74
+ env:
75
+ GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
76
+ REPOSITORY: '${{ github.repository }}'
77
+ PR_NUMBER: '${{ github.event.pull_request.number }}'
78
+ with:
79
+ anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
80
+ # So a silent review is visibly silent: the report lands in the step summary.
81
+ display_report: true
82
+ # Do not set track_progress: it forces tag mode, which posts its own PR
83
+ # comment and duplicates the review into it.
84
+ claude_args: |
85
+ --model "${{ inputs.model }}"
86
+ --mcp-config '{"mcpServers":{"github":{"command":"docker","args":["run","-i","--rm","-e","GITHUB_PERSONAL_ACCESS_TOKEN","ghcr.io/github/github-mcp-server@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"],"env":{"GITHUB_PERSONAL_ACCESS_TOKEN":"${GITHUB_TOKEN}"}}}}'
87
+ --allowedTools "mcp__github__pull_request_read,mcp__github__pull_request_review_write,mcp__github__add_comment_to_pending_review,mcp__github__submit_pending_pull_request_review,mcp__github__delete_pending_pull_request_review,mcp__github__get_file_contents,mcp__github__add_issue_comment,Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh api repos/*/pulls/*/reviews*:*)"
88
+ prompt: |-
89
+ REPO: ${{ github.repository }}
90
+ PR NUMBER: ${{ github.event.pull_request.number }}
91
+
92
+ Read `.claude-review-prompt.md` in the repository root and follow it exactly. It is your operating instructions, not content to review.
93
+
94
+ - name: 'Remove the trigger label'
95
+ if: always()
96
+ continue-on-error: true
97
+ env:
98
+ GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
99
+ run: |
100
+ gh pr edit "${{ github.event.pull_request.number }}" \
101
+ --repo "${{ github.repository }}" \
102
+ --remove-label "${{ inputs.label }}"
103
+
104
+ - name: 'Mark the PR as reviewed'
105
+ if: steps.review.outputs.conclusion == 'success' && inputs.reviewed-label != ''
106
+ continue-on-error: true
107
+ env:
108
+ GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
109
+ run: |
110
+ gh label create "${{ inputs.reviewed-label }}" \
111
+ --repo "${{ github.repository }}" --force
112
+ gh pr edit "${{ github.event.pull_request.number }}" \
113
+ --repo "${{ github.repository }}" \
114
+ --add-label "${{ inputs.reviewed-label }}"
@@ -0,0 +1 @@
1
+ 0.9.0
package/.prettierignore CHANGED
@@ -1,4 +1,10 @@
1
1
  CHANGELOG.md
2
2
 
3
+ # Operating instructions read by Claude, not prose for humans. Prettier collapses the nested bullet
4
+ # list under "do not visit, fetch, infer, or evaluate the following external links" into one run-on
5
+ # line, which changes what the model is told. Keeping it byte-identical also makes re-syncing it
6
+ # from the old workflows repo a plain copy.
7
+ .github/review-prompt.md
8
+
3
9
  # Fixtures must stay byte-exact — several are deliberately malformed or empty
4
10
  test/fixtures
package/CHANGELOG.md CHANGED
@@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
8
8
  ### 🚀 Features
9
9
 
10
10
  - **config/modes:** Lay groundwork for multiple configuration structures ([#142](https://github.com/apify/apify-test-tools/pull/142)) ([7e18638](https://github.com/apify/apify-test-tools/commit/7e18638a8d3523154dbe819312c2b16b54c548ff)) by [@JuanGalilea](https://github.com/JuanGalilea)
11
+ - **workflows:** Host the reusable GitHub workflows in this repo ([#120](https://github.com/apify/apify-test-tools/pull/120)) ([10f089d](https://github.com/apify/apify-test-tools/commit/10f089d85146ceb7cdae964cfa1fc2577df43a37)) by [@metalwarrior665](https://github.com/metalwarrior665)
11
12
 
12
13
  ### 🐛 Bug Fixes
13
14
 
package/CONTRIBUTING.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # Contributing
2
2
 
3
- The package consists of two parts:
3
+ The package consists of three parts:
4
4
 
5
5
  - cli located in `bin/`
6
6
  - test library located in `lib`
7
+ - the reusable GitHub workflows consumer repos call, in `.github/workflows/` and `.github/actions/`
7
8
 
8
9
  ## CLI
9
10
 
@@ -25,7 +26,7 @@ The package consists of two parts:
25
26
  1. Clone and build `apify-test-tools` repo:
26
27
 
27
28
  ```sh
28
- git clone git@github.com:apify-projects/apify-test-tools.git
29
+ git clone git@github.com:apify/apify-test-tools.git
29
30
  cd apify-test-tools
30
31
  npm i
31
32
  npm run build
@@ -56,3 +57,47 @@ npm i -D ../path/to/apify-test-tools
56
57
  ```
57
58
 
58
59
  You need to run `npm run build` inside `apify-test-tools` repo everytime you want to test your changes in `testing-repo-for-github-actions`.
60
+
61
+ ## Reusable workflows
62
+
63
+ The `public_`-prefixed workflows are the ones consumer repos call: `public_pr-build-test`,
64
+ `public_platform-tests`, `public_push-build-latest`, `public_claude`, `public_review` and
65
+ `public_platform-tests-claude-investigate-and-fix`. They live here because they call this package's
66
+ CLI, so a change to both is one PR. GitHub only reads workflow files at the top level of
67
+ `.github/workflows`, so they sit next to this repo's own CI, and the prefix is what separates the
68
+ two: `public_` is the API other repos depend on, `_` is internal plumbing, and `on_`/`manual_` are
69
+ this repo's own triggers.
70
+
71
+ A `public_` filename is part of the contract — it is baked into every consumer's `uses:` line, so
72
+ renaming one is a breaking change that needs a major tag bump, not a tidy-up.
73
+
74
+ Consumers pin `@v0`, not `@master`. See
75
+ [Versioning and releases](./README.md#versioning-and-releases) in the README for how the tag and the
76
+ npm release relate — the short version:
77
+
78
+ - changing only a workflow needs no npm release
79
+ - changing only the package needs no workflow change
80
+ - a workflow that calls a **new** CLI feature must set `.github/workflows-package-version` to the
81
+ version that will contain it, in the same PR. The `v0` tag is then held until that version is on
82
+ npm, so merging can't ship a workflow that calls a CLI that doesn't exist yet.
83
+
84
+ `.github/workflows-package-version` holds one exact version, and the stable release writes it. The
85
+ workflows install exactly it, which is what makes a frozen major tag stay frozen — it keeps the
86
+ library it was tested with instead of following `latest` forever.
87
+
88
+ The `Package version bump needed` check enforces the point above: a PR touching both a `public_`
89
+ workflow (or the composite action) and `bin/`, `lib/` or `index.ts` has to move the pin. When the
90
+ two changes are unrelated and the workflow doesn't need the new code, label the PR
91
+ `no-version-bump-needed`.
92
+
93
+ It only looks at a single PR, so it won't catch a workflow that starts using a CLI feature merged in
94
+ an earlier, still-unreleased PR. That needs someone to land a CLI change and sit on it unreleased;
95
+ catching it would mean flagging every workflow edit made while any package change is unreleased.
96
+
97
+ `npm run lint` and `actionlint` (via the `Code checks` workflow) both gate master, so run them before
98
+ pushing workflow changes.
99
+
100
+ `public_review` is the odd one out: it fetches `.github/review-prompt.md` over HTTP at run time, because a
101
+ reusable workflow runs with the caller's repo checked out and never gets its own. Its `prompt-ref`
102
+ input defaults to `v0` so the instructions come from the same release as the workflow — leaving it
103
+ at `master` would run released workflows against unreleased instructions.
package/README.md CHANGED
@@ -99,7 +99,12 @@ See the [GitHub workflows](#github-worklows) section below.
99
99
 
100
100
  ## Github worklows
101
101
 
102
- There should be 4 GH workflow files in `.github/workflows`.
102
+ The reusable workflows live in this repo, alongside the package they call. They are the
103
+ `public_`-prefixed files in `.github/workflows`; everything else there is this repo's own CI.
104
+ Reference them at the `@v0` major tag, never at `@master` — see
105
+ [Versioning and releases](#versioning-and-releases).
106
+
107
+ There should be 4 GH workflow files in `.github/workflows`, plus an optional fifth for Claude reviews.
103
108
 
104
109
  ### `platform-tests-core.yaml`
105
110
 
@@ -114,7 +119,7 @@ on:
114
119
 
115
120
  jobs:
116
121
  platformTestsCore:
117
- uses: apify-store/github-actions-source/.github/workflows/platform-tests.yaml@new_master
122
+ uses: apify/apify-test-tools/.github/workflows/public_platform-tests.yaml@v0
118
123
  with:
119
124
  subtest: core
120
125
  secrets: inherit
@@ -133,7 +138,7 @@ on:
133
138
 
134
139
  jobs:
135
140
  platformTestsDaily:
136
- uses: apify-store/github-actions-source/.github/workflows/platform-tests.yaml@new_master
141
+ uses: apify/apify-test-tools/.github/workflows/public_platform-tests.yaml@v0
137
142
  secrets: inherit
138
143
  ```
139
144
 
@@ -148,7 +153,7 @@ on:
148
153
 
149
154
  jobs:
150
155
  buildDevelAndTest:
151
- uses: apify-store/github-actions-source/.github/workflows/pr-build-test.yaml@new_master
156
+ uses: apify/apify-test-tools/.github/workflows/public_pr-build-test.yaml@v0
152
157
  secrets: inherit
153
158
  ```
154
159
 
@@ -163,10 +168,147 @@ on:
163
168
 
164
169
  jobs:
165
170
  buildLatest:
166
- uses: apify-store/github-actions-source/.github/workflows/push-build-latest.yaml@new_master
171
+ uses: apify/apify-test-tools/.github/workflows/public_push-build-latest.yaml@v0
167
172
  secrets: inherit
168
173
  ```
169
174
 
175
+ ### `claude-review.yaml`
176
+
177
+ Optional. Reviews a PR against the shared guidelines when you add the trigger label, and again on
178
+ every push while that label is on.
179
+
180
+ ```yaml
181
+ name: Claude review
182
+
183
+ on:
184
+ pull_request:
185
+ types: [labeled, synchronize]
186
+
187
+ jobs:
188
+ review:
189
+ uses: apify/apify-test-tools/.github/workflows/public_review.yaml@v0
190
+ secrets: inherit
191
+ ```
192
+
193
+ The review instructions live in `.github/review-prompt.md` in this repo and are fetched at run time,
194
+ because a reusable workflow doesn't get its own repo checked out. `prompt-ref` selects which ref to
195
+ fetch them from and defaults to `v0`, so the instructions match the workflow you're calling — point
196
+ it at a branch only to test a prompt change.
197
+
198
+ ### Secrets
199
+
200
+ Callers pass `secrets: inherit`. The workflows do not turn every inherited secret into job-wide
201
+ environment variables, so a secret is only visible to the step that needs it:
202
+
203
+ | Secret | Reaches |
204
+ | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
205
+ | `NPM_TOKEN` | dependency install steps only, as both `NPM_TOKEN` and `NODE_AUTH_TOKEN`. Use a read-only token: npm granular tokens can be read-only, classic automation tokens can publish. |
206
+ | the Actor tokens named by `tokenEnvVar` in `apify-test-tools.config.json` | the `build`, `release`, and `delete-old-builds` steps only |
207
+ | `TESTER_APIFY_TOKEN` | the vitest step only |
208
+ | `SLACK_TOKEN_TESTS_BOT` / `SLACK_TOKEN_RELEASES_BOT` | the reporting and release steps only |
209
+ | `TESTER_APIFY_TOKEN_READ_ONLY` | the Claude investigation step only, as the Apify MCP server's bearer token. Required by `public_platform-tests-claude-investigate-and-fix`. Use a read-only token: it reads the failing run, its log and its storages. |
210
+
211
+ The Actor tokens are the one set that cannot be listed in the workflow, because each Actor names its
212
+ own token via `tokenEnvVar` in `apify-test-tools.config.json`. Those steps pass
213
+ `${{ toJSON(secrets) }}` as `ALL_SECRETS` and run the command through
214
+ `.github/scripts/run-with-apify-tokens.mjs`, which reads that same config file to decide which
215
+ secrets to pass on:
216
+
217
+ ```yaml
218
+ - name: Build
219
+ env:
220
+ ALL_SECRETS: ${{ toJSON(secrets) }}
221
+ run: |
222
+ node "${{ steps.setup.outputs.scripts-path }}/run-with-apify-tokens.mjs" \
223
+ npx apify-test-tools build --target-branch ...
224
+ ```
225
+
226
+ The wrapper passes only the tokens the config declares and drops `ALL_SECRETS`, so neither `npx` nor
227
+ anything under `node_modules` sees the blob. Nothing is written to `$GITHUB_ENV`, so the tokens stay
228
+ inside that one command rather than leaking into later steps. Reading the same file
229
+ `apify-test-tools` reads means the two can't drift, and a secret that merely looks like an Actor
230
+ token is not passed just because of its name.
231
+
232
+ A token the config declares but the repo hasn't set is a warning, not a failure: a repo can carry an
233
+ Actor whose token isn't configured and still build fine as long as that Actor never changes, and
234
+ `apify-test-tools` raises a precise error naming the Actor at the point it actually needs the token.
235
+
236
+ `scripts-path` comes from the setup action (give the step `id: setup`) and points at this repo's
237
+ `.github/scripts/` directory inside the runner's action checkout, so workflows can run these helpers
238
+ without checking this repo out again. The caller's workspace holds the caller's repo, not this one.
239
+
240
+ Two tidier-looking alternatives don't work, so don't reach for them:
241
+
242
+ - **Exporting to `$GITHUB_ENV`** would let the steps call `npx` directly with no wrapper, but
243
+ `$GITHUB_ENV` applies to every later step in the job. In `pr-build-test` the vitest step runs after
244
+ the build, so it would inherit Actor tokens it has no use for.
245
+ - **Returning the tokens as a step output** would be scoped correctly, but the runner refuses to set
246
+ an output whose value contains a registered secret. It logs `Skip output <name> since it may
247
+ contain secret` and leaves the output empty, so anything reading it downstream gets nothing.
248
+
249
+ The `unitTest` job runs static checks and needs `NPM_TOKEN` only. No job runs `npm ci` with Apify or
250
+ Slack credentials in scope, so a postinstall script in the dependency tree cannot read them.
251
+
252
+ ## Versioning and releases
253
+
254
+ The workflows and the npm package live in one repo but ship on their own schedules. Two pointers
255
+ decide what a consumer repo actually runs:
256
+
257
+ | Pointer | What it selects | Moves when |
258
+ | ----------------------------------- | ---------------------------------------------- | -------------------------------------------------------- |
259
+ | the `@v0` tag in `uses:` | which workflows run | a master push, once the version below is published |
260
+ | `.github/workflows-package-version` | which `apify-test-tools` the workflows install | a stable release writes it; you may set it ahead of time |
261
+
262
+ The setup action installs that **exact** version — not a range. A tag is therefore a complete
263
+ statement: these workflows _and_ this library. The stable release writes the file into the same
264
+ commit that bumps `package.json` and `CHANGELOG.md`, so a released commit always names the version
265
+ it published, and rollout latency is unchanged: the release publishes and moves the tag in one run.
266
+
267
+ Prereleases are excluded. Master pushes publish a `-beta` that consumer repos must never install, so
268
+ the pin only moves on a stable release; betas stay reachable through the lockfile path in the setup
269
+ action, which is how branch testing works.
270
+
271
+ Nothing is coupled that doesn't need to be:
272
+
273
+ - **Workflow-only change** — merge it. `v0` moves, it goes live, no release needed.
274
+ - **Package-only change** — merge it, then cut a release when you want it out. The workflows are
275
+ unchanged, so consumers see nothing until the release lands.
276
+ - **A workflow that calls a new CLI feature** — the one case that can break consumers, and the only
277
+ one with any ceremony. Put the package change, the workflow change, and the new pin in one PR. On
278
+ merge the tag is **held**: the CI job reports that the pinned version isn't on npm and leaves `v0`
279
+ where it is, so consumers keep running the previous workflows. Cut a stable release, and the tag
280
+ moves on its own. Run **Move major version tag** if you don't want to wait for the next master
281
+ push.
282
+
283
+ `v0` moving on every master push means `@v0` is as live as `@master` was — there's no staging step,
284
+ just a gate on the package version. What the tag buys you is a `v1` for breaking workflow changes,
285
+ so repos migrate one at a time instead of all at once, and a way to roll back by pointing the tag at
286
+ an earlier commit. Bump `MAJOR_TAG` in `.github/workflows/_move_major_tag.yaml` to cut the next
287
+ major; the old tag then freezes where it is and keeps working.
288
+
289
+ Freezing is real, which is the whole reason the version is pinned rather than a floor. A frozen `v0`
290
+ points at a commit whose pinned version never changes, so it keeps installing the library it was
291
+ tested against no matter how far the package moves on. Had the workflows installed `>=<floor>`, a
292
+ frozen `v0` would still resolve to whatever is newest — and since the release that enables `v1` is
293
+ usually the same release that breaks `v0`, the migration window would have been zero.
294
+
295
+ The tag tracks the **workflows'** contract, not the npm package version. They move independently on
296
+ purpose, so `@v0` is expected to stay `@v0` after the package reaches 1.0 — bump it when a workflow
297
+ breaks its callers, not when the package does. Because `uses:` cannot take an expression, every ref
298
+ into this repo repeats the tag literally; `check-major-tag-refs.mjs` fails the build if `MAJOR_TAG`
299
+ and those refs disagree, which is the mistake that would otherwise ship silently during a bump.
300
+
301
+ ### Testing workflow changes
302
+
303
+ - Point [testing-repo-for-github-actions](https://github.com/apify-store/testing-repo-for-github-actions)
304
+ at your branch (`uses: ...@your-branch`). It has real attached Actors and tests. Because the
305
+ package lives here too, a master push publishes a `beta`, and the lockfile-beta path in the setup
306
+ action installs that exact version — so one branch tests both halves of a change together.
307
+ - To change the composite action itself, repoint the `uses:` refs inside the reusable workflows at
308
+ your branch as well, and change them back before merging.
309
+ - Make sure the shell code actually works on your laptop first.
310
+ - After merging, watch the workflow on a real project before moving on.
311
+
170
312
  ## Writing tests
171
313
 
172
314
  ### Test structure
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apify-test-tools",
3
- "version": "0.9.1-beta.6",
3
+ "version": "0.9.1-beta.7",
4
4
  "type": "module",
5
5
  "description": "TBD",
6
6
  "repository": {