Repository navigation
refactor: test reporting and notifications into separate commands - #223
metalwarrior665 wants to merge 14 commits into
Conversation
`test` wraps the repository's own vitest: it reads the builds from `build` on stdin (or tests every default build with `--all`), sets ACTOR_BUILDS / RUN_ALL_PLATFORM_TESTS and the concurrency flags itself, sends vitest's output to stderr, logs every test with its run link, and prints a versioned JSON report on stdout. Test files that fail to load and vitest crashing without a report now count as failures; `report-tests` silently ignored them. `notify` reads that report and posts it to any number of Slack channels (`--slack-channel`, repeatable), on failure or `--on always`, with the title and job link defaulting to the GitHub Actions run. Targets are pluggable so email can be added next to Slack. Thread replies are split to stay within Slack's block limits, and test text is escaped. The platform-tests workflow now runs `test` and `notify` as separate steps, so the Slack token no longer reaches the repository's test code. The PR workflow pipes the builds into `test` through a file instead of a step output. BREAKING CHANGE: `report-tests` is removed; use `test | notify`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149HKZkpP35zMLDjVKKf6Cm
`notify` now takes either the report of `test` or the output of `get-affected-actors` on stdin, told apart by their `tests` and `commits` fields. For a release, `--slack-channel` gets the commits and changed files (what `--report-slack-channel` did) and the new `--slack-changelog-channel` gets the changelog additions (what `--release-slack-channel` did); both can be repeated. Flags that only fit the other kind of input are an error instead of being ignored. Tokens stay per kind: SLACK_TOKEN_TESTS_BOT for test runs, SLACK_TOKEN_RELEASES_BOT for releases. The release announcement moves from slack.ts to notify.ts and goes through the same Slack target as test notifications; its messages are unchanged. BREAKING CHANGE: `notify-release` is removed. Use `notify` with `--slack-changelog-channel` in place of `--release-slack-channel` and `--slack-channel` in place of `--report-slack-channel`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149HKZkpP35zMLDjVKKf6Cm
`test` always runs vitest with --maxConcurrency=20 --fileParallelism --maxWorkers=100, the values every workflow used. The --max-concurrency and --max-workers flags go away; a one-off override still works after `--`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149HKZkpP35zMLDjVKKf6Cm
Vitest rejects an option given twice, so `test -- --maxConcurrency 5` failed with "Expected a single value". The hardcoded --maxConcurrency, --fileParallelism and --maxWorkers now step aside when the caller passes the same option (or its --no- form) after `--`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149HKZkpP35zMLDjVKKf6Cm
`notify` posts to one place per call; posting to more is calling it again. `--slack-channel` takes a single channel (a repeated flag is an error rather than a comma-joined channel), and `--slack-changelog-channel` is replaced by `--changelog-only`, which picks what a release message says instead of where it goes. The release workflow calls notify twice: the changelog to #delivery-public-actors, the full report to the repo's channel. The full release report now always includes the changelog additions; before, it did only when a changelog channel was also passed. BREAKING CHANGE: `--slack-channel` no longer repeats and `--slack-changelog-channel` is removed; call `notify --changelog-only --slack-channel <channel>` for the changelog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149HKZkpP35zMLDjVKKf6Cm
Master split the README into docs/cli.md, docs/lib.md and docs/workflows.md. This branch's README changes move there: the `test` and `notify` sections replace `report-tests` and `notify-release` in docs/cli.md, the local-run examples pipe builds into `test`, and docs/workflows.md describes the workflows' `test` and `notify` steps and where the Slack tokens reach. The CLI bundle is rebuilt from the merged sources. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149HKZkpP35zMLDjVKKf6Cm
Conflicts: - bin/main.ts: keeps the `test` and `notify` options next to master's --require-env-vars-in-sync; `notify-release` and its options stay removed. - public_pr-build-test.yaml: the build now passes --require-env-vars-in-sync and still writes builds.json for `test`. - public_platform-tests.yaml: master's TESTER_APIFY_TOKEN check runs before the `test` step. - The CLI bundle is rebuilt from the merged sources. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149HKZkpP35zMLDjVKKf6Cm
On GitHub Actions the runner sets GITHUB_SERVER_URL, GITHUB_REPOSITORY and GITHUB_RUN_ID, so notify appends the run link and "posts passing runs too with on: always" failed in CI while passing locally. Every test now starts without them; the one that checks the defaults sets them itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149HKZkpP35zMLDjVKKf6Cm
- Test reports and releases both post with SLACK_BOT_TOKEN, replacing SLACK_TOKEN_TESTS_BOT and SLACK_TOKEN_RELEASES_BOT in the CLI, the workflows' secret declarations and the docs. - `--changelog-only` is removed, and the release workflow posts once, the full message to the report channel; nothing goes to #delivery-public-actors. - The release message drops the "Additions to the changelog" header, the "Commit message" prefix and the changed files. Each commit is its title, cut at 200 characters, linked to its GitHub commit page (plain text on other hosts), followed by its author. A rebase merge lists every commit. - `notify` no longer reads `changedFiles` from the get-affected-actors output. BREAKING CHANGE: callers pass SLACK_BOT_TOKEN instead of SLACK_TOKEN_TESTS_BOT and SLACK_TOKEN_RELEASES_BOT, and `notify --changelog-only` is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149HKZkpP35zMLDjVKKf6Cm
…om the CLI - `test` no longer passes arguments after `--` to vitest. Nothing used it, and it let a caller replace the reporters the report depends on. The parallelism flags are fixed again, so the duplicate-option handling goes too. Running vitest directly with ACTOR_BUILDS still covers one-off options. - `notify` no longer reads GITHUB_* variables for its title and job link: they are plain `--title` and `--job-url` parameters, as `report-tests` had them, and the platform-tests workflow passes the workflow name and the run URL. The CLI stays CI-agnostic. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149HKZkpP35zMLDjVKKf6Cm
…test Without the token the lib skips every platform test, so `test` passed having tested nothing, with only a warning. It now fails before starting vitest. The workflows keep their own early check, which in the PR workflow also stops the job before any Actor is built. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149HKZkpP35zMLDjVKKf6Cm
Docs and comments state how things work now; how they used to work belongs in PR descriptions. Rewrites the `test --all` paragraph, the change-detection note on `code/` and `shared/`, the git range commands section, the workflows' `report-slack-channel` comments, the release-guard note in CONTRIBUTING.md, the `devel` tag comment in build.ts and one test name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149HKZkpP35zMLDjVKKf6Cm
… a quiet Notify step - `test --reporter <name>` adds vitest reporters next to the default and JSON ones. Both workflows pass `github-actions`, so failing tests are annotated on the run again; passing reporters replaced vitest's own default, which adds it on GitHub Actions. - The Slack failure message is the one report-tests posted: "has N failed assertions. Failing test suites: F/T.", one line per failure message as `message --- <runLink|actorId>`, the title defaulting to `-`. The report keeps every failure message again, so the counts match. Text from the tests is escaped, a failure without a run gets no link, and errors outside of tests are counted and listed. - The scheduled workflow's Notify step runs only when the Test step ran, and skips an empty report, so a failed token check no longer adds a second red step with a confusing error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149HKZkpP35zMLDjVKKf6Cm
| # secrets: | ||
| # TESTER_APIFY_TOKEN: ${{ secrets.TESTER_APIFY_TOKEN }} | ||
| # SLACK_TOKEN_TESTS_BOT: ${{ secrets.SLACK_TOKEN_TESTS_BOT }} | ||
| # SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} |
There was a problem hiding this comment.
Merged both Slack tokens to this one. I already added SLACK_BOT_TOKEN to our orgs
| SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} | ||
| run: | | ||
| apify-test-tools notify \ | ||
| --slack-channel "${{ inputs.report-slack-channel || format('#notif-{0}', github.event.repository.name) }}" \ |
There was a problem hiding this comment.
Keeping the channel fallback now before teams add the workflow inputs. We remove the fallback before releasing this to community.
| @@ -0,0 +1,221 @@ | |||
| import process from 'node:process'; | |||
There was a problem hiding this comment.
This file should be 1:1 with current code, just with few small UX improvements
| const SLACK_TOKEN_ENV_VAR = 'SLACK_BOT_TOKEN'; | ||
|
|
||
| // Long enough for any sensible commit title, short enough that one runaway title doesn't swamp the message | ||
| const MAX_COMMIT_TITLE_LENGTH = 200; |
There was a problem hiding this comment.
Commit messages could spam a page of text before
| ? `${message.slice(0, MAX_COMMIT_TITLE_LENGTH - 1)}…` | ||
| : message; | ||
| const url = getCommitUrl(repoUrl, sha); | ||
| const linked = url ? `<${url}|${escapeSlack(title)}>` : escapeSlack(title); |
There was a problem hiding this comment.
Commits now contain links to them
| SLACK_TOKEN_RELEASES_BOT: ${{ secrets.SLACK_TOKEN_RELEASES_BOT }} | ||
| SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} | ||
| run: | | ||
| apify-test-tools notify-release --release-slack-channel "#delivery-public-actors" --report-slack-channel "${{ inputs.report-slack-channel || format('#notif-{0}', github.event.repository.name) }}" < affected.json |
There was a problem hiding this comment.
No more 2 channels, #delivery-public-actors is dead
…nch (#62) * test: run the workflows from apify-test-tools' test and notify branch Points every workflow at apify/apify-test-tools@claude/kind-lamport-dyov8p (apify/apify-test-tools#223) and touches src/main.ts so the PR workflow builds and tests Actors 1 and 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149HKZkpP35zMLDjVKKf6Cm * test: make both core tests fail on purpose To see how failing tests are reported: as annotations on this PR, and on Slack once the scheduled core tests run them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149HKZkpP35zMLDjVKKf6Cm * test: skip the env var sync check for this PR's builds dummy-for-testing-1's version 0.99 has env vars out of sync with 0.0 on purpose, which fails the build before the tests this PR is about. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149HKZkpP35zMLDjVKKf6Cm --------- Co-authored-by: Claude <noreply@anthropic.com>
Vitest's JSON reporter logs "JSON report written to <path>" whenever it writes its report to a file, which for `test` is a temporary file nobody needs. Vitest's stdout is now piped through `test` and passed on to stderr without that line. Colors stay on when stderr is a terminal, since vitest no longer sees one on its own stdout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0149HKZkpP35zMLDjVKKf6Cm
| 'release-slack-channel': { type: 'string', describe: 'Channel for the changelog additions only' }, | ||
| /** Flags of `test`, which runs the repository's vitest on the builds piped in from `build`. */ | ||
| const testOptions = { | ||
| all: { |
There was a problem hiding this comment.
bad name, on the workflow I thought it meant test all actors (which made no sense since you passed a blob) so i went looking for this definition.
Why not --latest or --default-build
| 'vitest-output-file': { type: 'string', describe: "Also keep vitest's own JSON report there, e.g. to upload it" }, | ||
| reporter: { | ||
| type: 'string', | ||
| describe: 'Vitest reporter to add to the default one, e.g. github-actions. Repeat it for more', |
There was a problem hiding this comment.
docs link would be useful here.
Also mention the ones set by default (default and json)
| .option('report-slack-channel', { type: 'string' }) | ||
| .option('job-url', { type: 'string' }) | ||
| .option('workflow-name', { type: 'string' }), | ||
| .positional('filters', { |
There was a problem hiding this comment.
being this positional and defined first, does it even collide with --reporter and its fake array?
| /** A flag the input has no use for would be dropped silently, so it fails instead: the caller meant something by it. */ | ||
| const assertFlagsFitInput = ({ input, on, title, jobUrl, repoUrl }: NotifyOptions) => { | ||
| const misfits = | ||
| input.kind === 'tests' |
There was a problem hiding this comment.
nit: ternary for "enum" means we'll have to restructure the whole thing if we ever get a different use case (like test summaries).
Did a snippet of how it could look on a modification-safe way:
const misfits: string[] = [];
let stdinInputDescriptor: string;
switch (input.kind) {
case 'tests':
stdinInputDescriptor = 'a test report (the output of test)';
if (changelogOnly) misfits.push('--changelog-only');
if (repoUrl) misfits.push('--repo-url');
break;
case 'release':
stdinInputDescriptor = 'a release (the output of get-affected-actors)';
if (on !== 'failure') misfits.push('--on');
if (title) misfits.push('--title');
if (jobUrl) misfits.push('--job-url');
break;
default:
throw unreachable(input);
}
if (misfits.length > 0) {
throw new Error(`${misfits.join(', ')} can't be used with ${stdinInputDescriptor} on stdin`);
}Probably overkill tho
There was a problem hiding this comment.
did a nit comment about input.kind and its possible modification on assertFlagsFitInput.
Looking at the file as a whole, this seems like a place for strategy pattern (instead of that switch) since we're bound to have split behavior for all different types of it
There was a problem hiding this comment.
branding our outputs (as in with a kind prop) would make our lives easier. (not to be solved here)
| 'e.g. apify-test-tools test --all | apify-test-tools notify --slack-channel "#my-channel", ' + | ||
| 'or apify-test-tools get-affected-actors --since <sha> | apify-test-tools notify --slack-channel "#my-channel"'; | ||
| const expected = 'the output of test or get-affected-actors'; | ||
| const json = parseStdinJson(raw, z.unknown(), { expected, example }); |
There was a problem hiding this comment.
Checking for it being an object at least helps with the has. An unknown is pretty much useless
| const json = parseStdinJson(raw, z.unknown(), { expected, example }); | |
| const json = parseStdinJson(raw, z.looseObject({}), { expected, example }); |
Check
#notif-testing-repo-for-github-actionschannel to see live test of this branch.AI Summary
This PR refactors the test reporting and notification system by splitting the monolithic
report-testscommand into two focused commands:test(which runs vitest and produces a report) andnotify(which sends notifications for both test results and releases).Key Changes
New
testcommand (bin/run-tests.ts): Runs the repository's vitest suite on specified builds or all Actors, producing a structuredTestReportthat replaces the previous JSON parsing approach. Handles vitest binary resolution from the repository'snode_modulesand manages parallelism settings.New
notifycommand (bin/notify.ts): Unified notification system that handles both test reports and release announcements. Replaces the separatereport-testsandnotify-releasecommands with a single entry point that reads different input types via stdin.Refactored
test-report.ts: Converted from a reporting script into a data structure and conversion module. DefinesTestReport,TestCaseReport, andTestRuninterfaces that serve as the contract betweentestandnotify. IncludestoTestReport()to convert vitest's JSON output to the standardized format.Updated
stdin-input.ts: Added parsers for test builds (parseTestBuilds) and unified notify input (parseNotifyInput), replacing the previousparseReleaseChanges. Validates that flags match the input type (e.g.,--changelog-onlyonly works with releases).Simplified
slack.ts: Removed release-specific notification logic; now only provides low-level Slack client utilities (getSlackClient,sendSlackMessage,sendingLabel).Updated CLI entry point (
bin/main.ts): Registered newtestandnotifycommands with their respective options. Removedreport-testsandnotify-releasecommands.Updated workflows: Modified GitHub Actions workflows to use the new
testandnotifycommands instead of the oldreport-testsandnotify-release.Notable Implementation Details
testcommand resolves vitest from the repository'snode_modulesto ensure tests use the locked version, not the bundled CLI's version.notifycreates notification targets (currently Slack only) only when a message may be sent, so broken tokens are caught early but unnecessary token checks are avoided.TestReportformat is versioned (v1) to allow future evolution while maintaining backward compatibility.https://claude.ai/code/session_0149HKZkpP35zMLDjVKKf6Cm