diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 605e976..521b519 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -56,15 +56,33 @@ jobs: steps: - uses: actions/checkout@v4 - # enablement turns Pages on for the repository if it is off, so the - # published link works without anyone visiting the settings page first. - - uses: actions/configure-pages@v5 - with: - enablement: true + # Publishing needs Pages switched on once, by hand, with Source set to + # "GitHub Actions" in Settings -> Pages. It cannot be automated from here: + # creating a Pages site needs administration:write, which a workflow token + # cannot be granted, and the REST call is refused. Left to itself + # configure-pages reports that as "Resource not accessible by + # integration", which reads like a broken workflow rather than a setting + # nobody has set, so the precondition is checked and named instead. + - id: site + name: Check that Pages is switched on + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if gh api "repos/$GITHUB_REPOSITORY/pages" >/dev/null 2>&1; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + echo "::warning title=Nothing was published::GitHub Pages is not enabled for this repository. Set Settings -> Pages -> Source to \"GitHub Actions\", then re-run this workflow. Creating the site needs administration:write, which no workflow token can hold." + fi + + - if: steps.site.outputs.exists == 'true' + uses: actions/configure-pages@v5 - - uses: actions/upload-pages-artifact@v3 + - if: steps.site.outputs.exists == 'true' + uses: actions/upload-pages-artifact@v3 with: path: web - id: deployment + if: steps.site.outputs.exists == 'true' uses: actions/deploy-pages@v4 diff --git a/web/test/browser.mjs b/web/test/browser.mjs index b8e7730..2c6565f 100644 --- a/web/test/browser.mjs +++ b/web/test/browser.mjs @@ -32,7 +32,13 @@ function parseArgs() { }; return { url: get("--url", "http://localhost:8123/index.html"), - shots: Number(get("--shots", 40)), + // One action of the harness: a placement, or a shot of its own. The bot's + // replies happen inside a single wait and do not count. The harness aims at + // a random legal ball on purpose, so it plays badly and its games run long + // in the tail — one here needed 40 and was still going. The cap is only + // here to catch a table that has genuinely stopped making progress, so it + // sits well above that rather than close to the average. + shots: Number(get("--shots", 120)), games: Number(get("--games", 1)), screenshot: get("--screenshot", null), verbose: args.includes("--verbose"), @@ -128,7 +134,12 @@ async function main() { `${result.fouls} fouls, ` + (result.finished ? `winner ${result.winner}` : "shot limit reached") ); - if (!result.finished) throw new Error("game did not reach a conclusion"); + if (!result.finished) { + throw new Error( + `game did not reach a conclusion: ${result.shots} shots played in ` + + `${shotLimit} turns of the harness, phase "${result.phase}"` + ); + } if (result.botShots === 0) throw new Error("the bot never took a shot"); } if (screenshot) await page.screenshot({ path: screenshot, fullPage: true }); @@ -223,6 +234,7 @@ async function playGame(page, shotLimit, verbose = false) { potted: history.reduce((a, h) => a + h.potted.filter((n) => n !== 0).length, 0), fouls: history.filter((h) => h.foul).length, finished: final.phase === "over", + phase: final.phase, winner: final.winner, }; }