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
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env bash
2
+ # Registers every value inside the CONFIG_SECRETS secret as a mask.
3
+ #
4
+ # CONFIG_SECRETS=... bash .github/scripts/mask-config-secrets.sh
5
+ #
6
+ # The runner masks a secret by its exact value, so a value packed inside a JSON secret is only
7
+ # masked as part of that whole blob. Once a step takes it out, it would print in clear.
8
+ # Multi-line values are registered line by line, the way the runner registers secrets.
9
+ # CONFIG_SECRETS may be empty or unset.
10
+
11
+ set -uo pipefail
12
+
13
+ [ -n "${CONFIG_SECRETS:-}" ] || exit 0
14
+
15
+ # jq's errors can quote the input, so they are dropped for our own message
16
+ jq -r 'if type != "object" then error else . end | to_entries[] | .value | strings | split("\n")[] | select(. != "") | "::add-mask::" + .' <<< "$CONFIG_SECRETS" 2>/dev/null \
17
+ || { echo "::error::The CONFIG_SECRETS secret must be a JSON object."; exit 1; }
@@ -1,13 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  // Runs apify-test-tools and passes it only secrets needed for the Actors, no other secrets are leaked.
3
3
  //
4
- // node .github/scripts/run-with-apify-tokens.mjs npx apify-test-tools build --target-branch ...
4
+ // jq ... | node .github/scripts/run-with-apify-tokens.mjs apify-test-tools build --version-number 0.99
5
5
  //
6
- // The step passes the whole secrets map as ALL_SECRETS. The token names come from `tokenEnvVar` in
7
- // the repo's apify-test-tools.config.json, which is the same file apify-test-tools itself reads, so
8
- // the two can't drift. Everything else in the secrets map (npm, Slack, GitHub, anything else the
9
- // repo holds) is left out, and ALL_SECRETS itself is dropped, so the blob never reaches npx or
10
- // anything under node_modules.
6
+ // The step passes the reusable workflow's whole secrets map as WORKFLOW_SECRETS. The token names
7
+ // come from `tokenEnvVar` in the repo's apify-test-tools.config.json, which is the same file
8
+ // apify-test-tools itself reads, so the two can't drift. Everything else in the secrets map (npm,
9
+ // Slack, GitHub, anything else the repo holds) is left out, and WORKFLOW_SECRETS itself is dropped,
10
+ // so the blob never reaches the CLI or anything under node_modules.
11
+ //
12
+ // Each token is looked up in the CONFIG_SECRETS secret first, a JSON object of secret name -> value,
13
+ // and then among the top-level secrets, which is where `secrets: inherit` puts them. The secrets the
14
+ // workflows read by name (NPM_TOKEN, TESTER_APIFY_TOKEN, ...) never come from CONFIG_SECRETS.
11
15
  //
12
16
  // The command runs without a shell, so branch names and other interpolated arguments are passed
13
17
  // through as literal argv entries.
@@ -28,16 +32,36 @@ const fail = (message) => {
28
32
  const [command, ...args] = process.argv.slice(2);
29
33
  if (!command) fail('Usage: run-with-apify-tokens.mjs <command> [args...]');
30
34
 
31
- const { ALL_SECRETS, ...baseEnv } = process.env;
32
- if (!ALL_SECRETS) fail('ALL_SECRETS is not set. Add `ALL_SECRETS: ${{ toJSON(secrets) }}` to the step env.');
35
+ const { WORKFLOW_SECRETS, ...baseEnv } = process.env;
36
+ if (!WORKFLOW_SECRETS) {
37
+ fail('WORKFLOW_SECRETS is not set. Add `WORKFLOW_SECRETS: ${{ toJSON(secrets) }}` to the step env.');
38
+ }
33
39
 
34
- let secrets;
40
+ let workflowSecrets;
35
41
  try {
36
- secrets = JSON.parse(ALL_SECRETS);
42
+ workflowSecrets = JSON.parse(WORKFLOW_SECRETS);
37
43
  } catch (error) {
38
- fail(`ALL_SECRETS is not valid JSON: ${error.message}`);
44
+ fail(`WORKFLOW_SECRETS is not valid JSON: ${error.message}`);
39
45
  }
40
46
 
47
+ // Never includes the parse error: Node quotes the input in it, and the input is a secret.
48
+ const unpackConfigSecrets = () => {
49
+ const packed = workflowSecrets.CONFIG_SECRETS;
50
+ if (!packed) return {};
51
+ let value;
52
+ try {
53
+ value = JSON.parse(packed);
54
+ } catch {
55
+ fail('The CONFIG_SECRETS secret is not valid JSON.');
56
+ }
57
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
58
+ fail('The CONFIG_SECRETS secret must be a JSON object.');
59
+ }
60
+ return value;
61
+ };
62
+
63
+ const secrets = { ...workflowSecrets, ...unpackConfigSecrets() };
64
+
41
65
  const configPath = path.resolve(process.cwd(), CONFIG_FILE_NAME);
42
66
  let config;
43
67
  try {
@@ -78,7 +102,8 @@ const missing = tokenNames.filter((name) => !(name in secrets));
78
102
  if (missing.length > 0) {
79
103
  console.error(
80
104
  `Warning: ${CONFIG_FILE_NAME} declares ${missing.join(', ')}, but no such secret was passed ` +
81
- `to this workflow. Check the repository secrets if a build fails on a missing token.`,
105
+ `to this workflow, neither inside CONFIG_SECRETS nor inherited. Check the repository secrets ` +
106
+ `and the workflow's \`secrets:\` if a build fails on a missing token.`,
82
107
  );
83
108
  }
84
109
 
@@ -26,22 +26,6 @@ jobs:
26
26
  # - name: Check spelling with typos
27
27
  # uses: crate-ci/typos@v1
28
28
 
29
- major_tag_refs:
30
- name: Major tag refs
31
- runs-on: ubuntu-latest
32
- steps:
33
- - name: Checkout repository
34
- uses: actions/checkout@v6
35
- - name: Use Node.js
36
- uses: actions/setup-node@v6
37
- with:
38
- node-version: 24
39
- # `uses:` takes no expressions, so the major tag is repeated in every ref into this repo.
40
- # Missing one during a bump is silent while both tags exist: the new workflows quietly
41
- # run the old composite action.
42
- - name: Check refs match MAJOR_TAG
43
- run: node .github/scripts/check-major-tag-refs.mjs
44
-
45
29
  lint_check:
46
30
  name: Lint check
47
31
  runs-on: ubuntu-latest
@@ -92,3 +76,28 @@ jobs:
92
76
  run: npm ci
93
77
  - name: Build
94
78
  run: npm run build
79
+
80
+ # The composite action runs the CLI from .github/cli/apify-test-tools.mjs, which the pre-commit
81
+ # hook rebuilds. This catches commits that skipped the hook, so a stale bundle never ships.
82
+ cli_bundle:
83
+ name: CLI bundle
84
+ runs-on: ubuntu-latest
85
+ steps:
86
+ - name: Checkout repository
87
+ uses: actions/checkout@v6
88
+ - name: Use Node.js
89
+ uses: actions/setup-node@v6
90
+ with:
91
+ node-version: 24
92
+ cache: 'npm'
93
+ cache-dependency-path: 'package-lock.json'
94
+ - name: Install dependencies
95
+ run: npm ci
96
+ - name: Check the bundle is up to date
97
+ run: |
98
+ npm run bundle
99
+ if ! git diff --quiet -- .github/cli/; then
100
+ echo "::error::.github/cli/apify-test-tools.mjs is out of date. Run \`npm run bundle\` and commit the result."
101
+ exit 1
102
+ fi
103
+ node .github/cli/apify-test-tools.mjs --help > /dev/null
@@ -1,13 +1,8 @@
1
1
  name: Move major version tag
2
2
 
3
- # Consumer repos track a floating major tag (`...@v0`), not `master`, so merging to master does not
4
- # ship anything by itself. This workflow is what ships: it moves that tag forward, but only once the
5
- # package version the workflows pin in .github/workflows-package-version is actually on npm.
6
- #
7
- # That gate is the whole point. The workflows and the npm package release on their own schedules, so
8
- # the only combination that can break consumers is a workflow that calls a CLI feature which has not
9
- # been published yet. In that case the tag simply stays where it is and production keeps running the
10
- # previous workflows until someone cuts a release.
3
+ # Consumer repos track a floating major tag (`...@workflows-v0`), not `master`. This workflow is what
4
+ # ships: it moves that tag forward. The workflows and the CLI they call (bundled in .github/cli/)
5
+ # live in the same commit, so whatever the tag points at is consistent on its own.
11
6
 
12
7
  on:
13
8
  workflow_call:
@@ -16,13 +11,6 @@ on:
16
11
  description: Commit to move the tag to. Defaults to the commit that triggered the caller.
17
12
  type: string
18
13
  default: ''
19
- wait_for_publish:
20
- description: >-
21
- Set by a release that has just published the pinned version. npm accepts a publish
22
- before the version is readable, so the check waits instead of treating the gap as
23
- a held tag, and fails if the version never appears.
24
- type: boolean
25
- default: false
26
14
 
27
15
  permissions:
28
16
  contents: read
@@ -54,8 +42,8 @@ jobs:
54
42
  # it. Depending on a regex in another repo to keep releases working is not a dependency
55
43
  # worth having, and `v1` beside a package `v1.0.0` would be worse still.
56
44
  #
57
- # `uses:` cannot take an expression, so every ref into this repo hardcodes this same tag;
58
- # check-major-tag-refs.mjs fails the build if one of them drifts.
45
+ # The workflows load this repo with `$/`, so they never name the tag. Only the usage
46
+ # examples in the workflows, README and docs do: update them by hand when bumping it.
59
47
  MAJOR_TAG: workflows-v0
60
48
  steps:
61
49
  - name: Checkout repository
@@ -63,54 +51,7 @@ jobs:
63
51
  with:
64
52
  ref: ${{ inputs.ref || github.sha }}
65
53
 
66
- - name: Check the required package version is published
67
- id: check
68
- env:
69
- # npm accepts a publish and processes it asynchronously ("Your package is being
70
- # processed and may take a few minutes to become available"). Observed at ~19
71
- # minutes for a provenance-signed release, so the release path waits well past
72
- # npm's own estimate rather than shipping a tag consumers cannot install against.
73
- WAIT_SECONDS: ${{ inputs.wait_for_publish && 2400 || 0 }}
74
- run: |
75
- pinned_version=$(cat .github/workflows-package-version)
76
- echo "pinned_version=$pinned_version" >> "$GITHUB_OUTPUT"
77
-
78
- deadline=$(( $(date +%s) + WAIT_SECONDS ))
79
- while :; do
80
- # --prefer-online so a 404 cached from an earlier attempt cannot outlive the
81
- # publish it was polling for.
82
- if output=$(npm view "apify-test-tools@$pinned_version" version --prefer-online 2>&1); then
83
- echo "apify-test-tools@$pinned_version is on npm"
84
- echo "published=true" >> "$GITHUB_OUTPUT"
85
- exit 0
86
- fi
87
-
88
- # Anything that is not "this version does not exist" is a registry, network or
89
- # auth problem. Sitting in the retry loop would hide it behind a held tag.
90
- if ! grep -q 'code E404' <<< "$output"; then
91
- echo "::error::npm view failed for a reason other than the version being absent."
92
- echo "$output"
93
- exit 1
94
- fi
95
-
96
- if [ "$(date +%s)" -ge "$deadline" ]; then
97
- break
98
- fi
99
- echo "apify-test-tools@$pinned_version is not on npm yet, retrying in 30s"
100
- sleep 30
101
- done
102
-
103
- echo "published=false" >> "$GITHUB_OUTPUT"
104
-
105
- # A release waited for its own publish and never saw it. That is a broken release,
106
- # not a held tag, and it has to be red so it can be re-run once npm catches up.
107
- if [ "$WAIT_SECONDS" -gt 0 ]; then
108
- echo "::error::apify-test-tools@$pinned_version was published by this run but is still not readable after $((WAIT_SECONDS / 60))m. Re-run this job once it appears on npm."
109
- exit 1
110
- fi
111
-
112
54
  - name: Move tag
113
- if: steps.check.outputs.published == 'true'
114
55
  env:
115
56
  TARGET_SHA: ${{ inputs.ref || github.sha }}
116
57
  run: |
@@ -120,23 +61,4 @@ jobs:
120
61
  echo "### \`$MAJOR_TAG\` moved"
121
62
  echo
122
63
  echo "Now points at \`$TARGET_SHA\`."
123
- echo "Pins \`apify-test-tools@${{ steps.check.outputs.pinned_version }}\`, which is published."
124
- } >> "$GITHUB_STEP_SUMMARY"
125
-
126
- # Not a failure. Holding the tag is the designed outcome of merging a workflow change
127
- # that needs an unreleased package feature, and consumers are unaffected while it waits.
128
- - name: Report held tag
129
- if: steps.check.outputs.published != 'true'
130
- env:
131
- TARGET_SHA: ${{ inputs.ref || github.sha }}
132
- run: |
133
- {
134
- echo "### \`$MAJOR_TAG\` held"
135
- echo
136
- echo "\`$TARGET_SHA\` pins \`apify-test-tools@${{ steps.check.outputs.pinned_version }}\`, which is not on npm."
137
- echo "Consumers keep running the workflows \`$MAJOR_TAG\` already points at."
138
- echo
139
- echo "Publish that version with the **Stable release** workflow. The tag moves on the"
140
- echo "next master push, or immediately via the **Move major version tag** workflow."
141
64
  } >> "$GITHUB_STEP_SUMMARY"
142
- echo "::warning::$MAJOR_TAG not moved: apify-test-tools@${{ steps.check.outputs.pinned_version }} is not published yet."
@@ -3,6 +3,10 @@ name: Tests
3
3
  on:
4
4
  workflow_dispatch:
5
5
  workflow_call:
6
+ secrets:
7
+ # Token of an isolated sandbox account that can only reach the e2e test's two dummy Actors.
8
+ E2E_TEST_APIFY_TOKEN_DBYPODOIHUZABNQTR:
9
+ required: false
6
10
 
7
11
  permissions:
8
12
  contents: read
@@ -33,3 +37,33 @@ jobs:
33
37
 
34
38
  - name: Unit tests
35
39
  run: npm test
40
+
41
+ # Runs the shipped CLI bundle against real Actors (test/e2e). Node 24 only, so one push builds
42
+ # and runs the sandbox Actors once rather than once per Node version.
43
+ e2e_tests:
44
+ name: E2E tests
45
+ runs-on: ubuntu-latest
46
+ steps:
47
+ - name: Checkout repository
48
+ uses: actions/checkout@v6
49
+
50
+ - name: Use Node.js
51
+ uses: actions/setup-node@v6
52
+ with:
53
+ node-version: 24
54
+ cache: 'npm'
55
+ cache-dependency-path: 'package-lock.json'
56
+
57
+ - name: Install dependencies
58
+ run: npm ci
59
+
60
+ # The token is only on this step. Fork and Dependabot PRs don't get the secret, and the
61
+ # suite skips itself without it.
62
+ - name: E2E tests
63
+ env:
64
+ E2E_TEST_APIFY_TOKEN_DBYPODOIHUZABNQTR: ${{ secrets.E2E_TEST_APIFY_TOKEN_DBYPODOIHUZABNQTR }}
65
+ run: |
66
+ if [ -z "$E2E_TEST_APIFY_TOKEN_DBYPODOIHUZABNQTR" ]; then
67
+ echo "::notice::E2E_TEST_APIFY_TOKEN_DBYPODOIHUZABNQTR is not available to this run, the e2e tests are skipped."
68
+ fi
69
+ npm run test:e2e
@@ -77,18 +77,6 @@ jobs:
77
77
  write-mode: overwrite
78
78
  contents: ${{ needs.release_metadata.outputs.changelog }}
79
79
 
80
- # The reusable workflows install this exact version, so the commit that releases a
81
- # version is the commit that pins it. That is what lets a frozen major tag keep
82
- # installing the library it was tested against instead of drifting to whatever is
83
- # newest.
84
- #
85
- # Skipped for prereleases: master pushes release a `-beta` that consumer repos must
86
- # never install. Their pin stays on the last stable, and the beta stays reachable
87
- # through the lockfile path in the setup action, which is how branch testing works.
88
- - name: Update the version the workflows pin
89
- if: inputs.release_type != 'prerelease'
90
- run: echo "${{ needs.release_metadata.outputs.version_number }}" > .github/workflows-package-version
91
-
92
80
  - name: Commit changes
93
81
  id: commit
94
82
  uses: apify/actions/signed-commit@v1.0.0
@@ -1,8 +1,8 @@
1
1
  name: Move major version tag
2
2
 
3
- # Escape hatch for the two cases the automatic move does not cover: shipping a workflow change right
4
- # after the release that unblocked it, without waiting for the next master push, and rolling the tag
5
- # back to an earlier commit when a shipped workflow turns out to be broken.
3
+ # Escape hatch for what the automatic move on master pushes does not cover: rolling the tag back to
4
+ # an earlier commit when a shipped workflow or CLI turns out to be broken, or moving it after a push
5
+ # whose checks failed for an unrelated reason.
6
6
 
7
7
  on:
8
8
  workflow_dispatch:
@@ -36,6 +36,9 @@ jobs:
36
36
  tests:
37
37
  name: Tests
38
38
  uses: ./.github/workflows/_tests.yaml
39
+ # Only the sandbox token, not `secrets: inherit`: the test job runs test code.
40
+ secrets:
41
+ E2E_TEST_APIFY_TOKEN_DBYPODOIHUZABNQTR: ${{ secrets.E2E_TEST_APIFY_TOKEN_DBYPODOIHUZABNQTR }}
39
42
 
40
43
  release_metadata:
41
44
  name: Update release metadata
@@ -79,16 +82,3 @@ jobs:
79
82
  "ref": "${{ needs.release_metadata.outputs.changelog_commitish }}",
80
83
  "tag": "latest"
81
84
  }
82
-
83
- # This release is what unblocks a major tag being held for an unpublished package version, so
84
- # try the move again now rather than making someone wait for the next master push. A no-op when
85
- # the tag was not being held.
86
- move_major_tag:
87
- name: Move major version tag
88
- needs: [release_metadata, npm_publish]
89
- permissions:
90
- contents: write
91
- uses: ./.github/workflows/_move_major_tag.yaml
92
- with:
93
- ref: ${{ needs.release_metadata.outputs.changelog_commitish }}
94
- wait_for_publish: true
@@ -24,14 +24,18 @@ jobs:
24
24
  if: "!startsWith(github.event.head_commit.message, 'docs')"
25
25
  name: Tests
26
26
  uses: ./.github/workflows/_tests.yaml
27
+ # Only the sandbox token, not `secrets: inherit`: the test job runs test code.
28
+ secrets:
29
+ E2E_TEST_APIFY_TOKEN_DBYPODOIHUZABNQTR: ${{ secrets.E2E_TEST_APIFY_TOKEN_DBYPODOIHUZABNQTR }}
27
30
 
28
- # Ships any workflow change in this push to consumer repos. Deliberately independent of the npm
29
- # release below: a workflow-only change needs no release, and a package-only release leaves the
30
- # workflows byte-identical. `code_checks` runs actionlint, which is what validates the workflows;
31
- # `tests` covers the npm package and is skipped for docs commits, so it is not a dependency here.
31
+ # Ships this push to consumer repos: the workflows and the CLI bundled in .github/cli/ go live
32
+ # together. Independent of the npm release below, which only matters to people importing the lib.
33
+ # `code_checks` runs actionlint and checks the bundle is up to date. `tests` cover the CLI but are
34
+ # skipped for docs commits, so a skipped run does not hold the tag; a failed one does.
32
35
  move_major_tag:
33
36
  name: Move major version tag
34
- needs: [code_checks]
37
+ needs: [code_checks, tests]
38
+ if: ${{ !failure() && !cancelled() }}
35
39
  permissions:
36
40
  contents: write
37
41
  uses: ./.github/workflows/_move_major_tag.yaml
@@ -21,51 +21,6 @@ jobs:
21
21
  tests:
22
22
  name: Tests
23
23
  uses: ./.github/workflows/_tests.yaml
24
-
25
- # Catches the one combination that breaks consumers: a workflow starting to use a CLI feature
26
- # from the same PR. Merging ships the workflow immediately, so the pinned version has to move in
27
- # the same change or the workflow goes live calling something that is not published.
28
- package_version_bump_needed:
29
- name: Package version bump needed
30
- runs-on: ubuntu-latest
31
- steps:
32
- - name: Checkout repository
33
- uses: actions/checkout@v6
34
- with:
35
- fetch-depth: 0
36
- ref: ${{ github.event.pull_request.head.sha }}
37
-
38
- - name: Check whether this PR needs a version bump
39
- env:
40
- BASE_REF: ${{ github.base_ref }}
41
- PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }}
42
- run: |
43
- git fetch --no-tags origin "$BASE_REF"
44
- git diff --name-only FETCH_HEAD...HEAD | node .github/scripts/check-package-version-bump.mjs
45
-
46
- # Reports whether merging this PR will ship the workflows or hold the major tag until a release.
47
- # Never fails: pinning a version ahead of the release that publishes it is a supported way to
48
- # land the package change and the workflow change that needs it in one PR.
49
- workflows_package_version:
50
- name: Workflows package version
51
- runs-on: ubuntu-latest
52
- steps:
53
- - name: Checkout repository
54
- uses: actions/checkout@v6
55
-
56
- - name: Check the pinned package version is published
57
- run: |
58
- pinned_version=$(cat .github/workflows-package-version)
59
- if npm view "apify-test-tools@$pinned_version" version >/dev/null 2>&1; then
60
- echo "Workflows pin apify-test-tools@$pinned_version, which is published."
61
- echo "The major tag moves on merge and these workflows go live." >> "$GITHUB_STEP_SUMMARY"
62
- else
63
- {
64
- echo "### Major tag will be held on merge"
65
- echo
66
- echo "These workflows pin \`apify-test-tools@$pinned_version\`, which is not on npm yet."
67
- echo "Merging is fine: consumers keep running the current workflows until you cut a"
68
- echo "stable release, and the tag moves once that version is published."
69
- } >> "$GITHUB_STEP_SUMMARY"
70
- echo "::notice::Merging will not ship these workflows until apify-test-tools $pinned_version is released."
71
- fi
24
+ # Only the sandbox token, not `secrets: inherit`: the test job runs test code.
25
+ secrets:
26
+ E2E_TEST_APIFY_TOKEN_DBYPODOIHUZABNQTR: ${{ secrets.E2E_TEST_APIFY_TOKEN_DBYPODOIHUZABNQTR }}
@@ -23,13 +23,17 @@ name: 'Platform tests: Claude failure investigation and fix'
23
23
  # uses: apify/apify-test-tools/.github/workflows/public_platform-tests.yaml@workflows-v0
24
24
  # with:
25
25
  # subtest: core
26
- # secrets: inherit
26
+ # secrets:
27
+ # TESTER_APIFY_TOKEN: ${{ secrets.TESTER_APIFY_TOKEN }}
28
+ # SLACK_TOKEN_TESTS_BOT: ${{ secrets.SLACK_TOKEN_TESTS_BOT }}
27
29
  #
28
30
  # handle-failure:
29
31
  # needs: platformTestsCore
30
32
  # if: ${{ failure() }}
31
33
  # uses: apify/apify-test-tools/.github/workflows/public_platform-tests-claude-investigate-and-fix.yaml@workflows-v0
32
- # secrets: inherit
34
+ # secrets:
35
+ # ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
36
+ # TESTER_APIFY_TOKEN: ${{ secrets.TESTER_APIFY_TOKEN }}
33
37
 
34
38
  on:
35
39
  workflow_call:
@@ -39,6 +43,11 @@ on:
39
43
  type: string
40
44
  default: sonnet
41
45
  description: Claude model to use for investigation and fix
46
+ effort:
47
+ required: false
48
+ type: string
49
+ default: ''
50
+ description: Claude effort level (low, medium, high, xhigh, max) to use for investigation and fix. Uses the Claude Code default when empty.
42
51
  max_flakiness_runs:
43
52
  required: false
44
53
  type: number
@@ -115,6 +124,7 @@ jobs:
115
124
  claude_args: |
116
125
  --mcp-config '{"mcpServers":{"apify":{"type":"http","url":"https://mcp.apify.com/?tools=get-actor-run,get-actor-run-log,get-dataset-list,get-dataset,get-dataset-items,get-key-value-store-list,get-key-value-store,get-key-value-store-keys,get-key-value-store-record","headers":{"Authorization":"Bearer ${TESTER_APIFY_TOKEN}"}}}}'
117
126
  --model ${{ inputs.model }}
127
+ ${{ inputs.effort && format('--effort {0}', inputs.effort) || '' }}
118
128
  --dangerously-skip-permissions
119
129
 
120
130
  - name: Upload Claude output (for debugging)
@@ -177,7 +187,9 @@ jobs:
177
187
  actions: read
178
188
  steps:
179
189
  - name: Setup repository and dependencies
180
- uses: apify/apify-test-tools/.github/actions/checkout-restore-dependencies@workflows-v0
190
+ # `$/` loads the action from this repo at the commit this workflow was called at, so a caller
191
+ # pinned to a SHA or a branch gets that commit's action and CLI, not whatever the major tag is on.
192
+ uses: $/.github/actions/checkout-restore-dependencies
181
193
  with:
182
194
  npm-token: ${{ secrets.NPM_TOKEN }}
183
195
 
@@ -190,6 +202,7 @@ jobs:
190
202
  prompt: 'Open a pull request to address GitHub issue #${{ needs.investigate.outputs.issue_number }}. Use `gh issue view ${{ needs.investigate.outputs.issue_number }}` to read the issue details first, then implement the fix and create a PR.'
191
203
  claude_args: |
192
204
  --model ${{ inputs.model }}
205
+ ${{ inputs.effort && format('--effort {0}', inputs.effort) || '' }}
193
206
  --dangerously-skip-permissions
194
207
 
195
208
  - name: Upload Claude output (for debugging)
@@ -18,6 +18,17 @@ on:
18
18
  type: string
19
19
  default: ''
20
20
  required: false
21
+ # Only TESTER_APIFY_TOKEN is required, every test needs it. See the Secrets section of docs/workflows.md.
22
+ secrets:
23
+ NPM_TOKEN:
24
+ required: false
25
+ description: Read-only token for installing private npm packages.
26
+ TESTER_APIFY_TOKEN:
27
+ required: true
28
+ description: Apify token the platform tests run Actors with.
29
+ SLACK_TOKEN_TESTS_BOT:
30
+ required: false
31
+ description: Slack token for reporting test results.
21
32
 
22
33
  jobs:
23
34
  scheduledPlatformTests:
@@ -26,11 +37,24 @@ jobs:
26
37
  runs-on: ubuntu-latest
27
38
  steps:
28
39
  - name: Setup repository and dependencies
29
- # For testing, you have to temporarily change the branch
30
- uses: apify/apify-test-tools/.github/actions/checkout-restore-dependencies@workflows-v0
40
+ # `$/` loads the action from this repo at the commit this workflow was called at, so a caller
41
+ # pinned to a SHA or a branch gets that commit's action and CLI, not whatever the major tag is on.
42
+ uses: $/.github/actions/checkout-restore-dependencies
31
43
  with:
32
44
  npm-token: ${{ secrets.NPM_TOKEN }}
33
45
 
46
+ # Fails here with a clear message rather than deep in vitest. `required: true` alone doesn't
47
+ # catch it: a caller can list the secret while the repo doesn't have it, and PRs from forks
48
+ # never get secrets.
49
+ - name: Check TESTER_APIFY_TOKEN
50
+ env:
51
+ TESTER_APIFY_TOKEN: ${{ secrets.TESTER_APIFY_TOKEN }}
52
+ run: |
53
+ if [ -z "$TESTER_APIFY_TOKEN" ]; then
54
+ echo "::error::TESTER_APIFY_TOKEN is empty. Pass it in the secrets: block of the job that calls this workflow."
55
+ exit 1
56
+ fi
57
+
34
58
  - name: Test
35
59
  env:
36
60
  TESTER_APIFY_TOKEN: ${{ secrets.TESTER_APIFY_TOKEN }}
@@ -47,7 +71,7 @@ jobs:
47
71
  --fileParallelism=true \
48
72
  --maxWorkers 100
49
73
  return_code=$?
50
- npx apify-test-tools report-tests \
74
+ apify-test-tools report-tests \
51
75
  --report-file ./test-output.json \
52
76
  --workflow-name "${{ github.workflow }}" \
53
77
  --job-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \