[v3] Run ExternalMode (stand) suite in CI in addition to LocalMode - #63
Merged
Merged
Conversation
…ted/ generated/ is gitignored, so CI has nothing to launch the stand Paper server with. Commit the launcher example_plugin/README.md already told developers to write by hand, under stand-run/, and pin *.sh to LF via .gitattributes so a Windows checkout doesn't break the shebang.
test-example-plugin only ever exercised LocalMode - the stand (ExternalMode) env is excluded from the matrix since it needs an already-running server, so the RCON console channel, account-pool leasing and the justCreated registration flow had zero CI coverage. The LocalMode run plugwright-action already does leaves Paper, cache and libraries under generated/local/run/ - stand points at the same localhost:25565. Copy in the tracked launcher, start it in the background, retry plugwrightPingStand until it answers, then run plugwrightTestStand. Kill the server and upload its log on failure regardless of outcome. ClosesDrownek#61.
monikon22
marked this pull request as draft
August 28, 2026 12:47
start.sh just runs whatever "java" is on PATH, which the earlier plugwright-action step pinned to 17 for the Gradle daemon. Paper 1.21.11 needs 21 (example_plugin/build.gradle.kts pins the toolchain there) - LocalMode never hit this because Gradle resolves and downloads that toolchain JDK itself for its own server launch, but a plain shell script has no such resolution. CI run 33172008840 confirmed the crash: UnsupportedClassVersionError, class file version 65.0 vs runtime's 61.0.
test-example-plugin and test-example-plugin-stand now run on separate runners concurrently instead of stand chaining off the end of a single sequential job. Stand can no longer reuse the local job's generated/local/run/ (different runner, different filesystem), so it provisions its own Paper server via plugwright-action's gradle-args input (plugwrightProvisionLocal only - not the full plugwrightTest, which would also run and duplicate the local suite). Everything after that (JDK 21 setup, start.sh, ping retry-loop, plugwrightTestStand, teardown, failure log upload) is unchanged, just moved into the new job. Trades a duplicate Paper/plugin download for roughly half the wall-clock time versus running sequentially in one job. job names: test-example-plugin keeps its existing name (a maintainer required-status-check on it, if any, keeps matching); the new job is test-example-plugin-stand.
plugwrightPingStand/plugwrightTestStand don't depend on plugwrightCompileTests (ExternalMode registers no prepareTask - registerTasks in ExternalMode.kt assumes the stand is already up and node_modules already has what it needs). The old sequential job got this for free as a side effect of plugwrightTest's dependsOn chain running first; the new parallel stand job only ran plugwrightProvisionLocal, so @plugwright/console-rcon was never installed and every ping failed with 'no console channel could be reached'. Verified locally: a clean node_modules, then ./gradlew plugwrightCompileTests, installs console-rcon/auth-authme/runner as expected.
The console-rcon fix's replace missed the pre-existing gradle-args line, leaving two under the same 'with:' block. Plain YAML parsers silently keep the last one (which is why local yaml.safe_load passed), but GitHub Actions' own parser rejects it outright - the run failed in 0s with zero jobs registered, no logs at all.
monikon22
marked this pull request as ready for review
August 28, 2026 13:22
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes#61.
test-example-pluginonly ever ran LocalMode —stand(ExternalMode) isincludeInMatrix: falsebecause it needs an already-running server, soplugwrightTestskips it and the RCON console channel, account-pool leasing andjustCreatedregistration flow had zero CI coverage.What changed
example_plugin/src/test/e2e/stand-run/start.sh(new, tracked): the launcherexample_plugin/README.mdalready told developers to hand-write undergenerated/local/run/. That directory is gitignored, so CI had nothing to copy in — this commits the same script README documents, outsidegenerated/..gitattributes(new): pins*.shto LF so a Windows checkout doesn't corrupt the shebang.example_plugin/README.md: one-line pointer to the tracked copy..github/workflows/ci.yml:standnow runs as its own job,test-example-plugin-stand, in parallel withtest-example-plugin(unchanged, keeps its name so an existing required-status-check on it still matches). Running on a separate runner means it can't reuse the local job'sgenerated/local/run/, so it:plugwright-action'sgradle-args: plugwrightProvisionLocal plugwrightCompileTests(provisioning alone isn't enough —plugwrightPingStand/plugwrightTestStanddon't depend onplugwrightCompileTeststhemselves, they just expectnode_modulesalready has whatconsole { rcon {} }and thenpm(...)plugin refs need; the old sequential job got this for free as a side effect ofplugwrightTest's own dependency chain).actions/setup-java@v4) before touchingstart.sh— Paper 1.21.11 needs it, but the Gradle daemon above runs on 17, andstart.shjust execs whateverjavais onPATH.start.shinto the run dir, launches it backgrounded, saves its PID../gradlew plugwrightPingStand(up to 30×2s) until the server answers — the built-in readiness probe (connects, probes the RCON console channel, leases + authenticates an account), not a raw port check../gradlew plugwrightTestStand— blocking, same as LocalMode.if: always(): kill the backgrounded server by PID.if: failure(): upload the server log +build/reports/plugwright/stand.*as an artifact.PLUGWRIGHT_RCON_PASSWORD/PLUGWRIGHT_BOT_PASSWORDare set at job level as literalplugwright— test-only credentials for a server the job itself starts and tears down within the same run, matchinglocal's existing default.No change to
plugwright-actionitself — its existinggradle-argsinput covered everything needed.Testing
Ran the sequence locally first:
plugwrightProvisionLocal→ copy trackedstart.shin → background start → ping retry-loop (ready on attempt 3) →plugwrightTestStand: 44/44 passed, 6 expected skips, 0 failed → kill by PID confirmed the process exits.CI itself went through a few iterations once the two jobs actually ran on separate runners (each caught something the single-job version had been masking as a side effect of task ordering):
UnsupportedClassVersionErrorstarting Paper 1.21.11 → fixed by installing JDK 21 beforestart.sh.plugwrightProvisionLocalalone doesn't install@plugwright/console-rcon→ every ping failed with "no console channel could be reached" → fixed by also runningplugwrightCompileTests.Final green run:
test-example-pluginpass in 4m58s,test-example-plugin-standpass in 4m52s, running concurrently — down from ~7m13s sequential.