diff --git a/.github/scripts/test-autoplay.js b/.github/scripts/test-autoplay.js index b4fd009..4bbbec4 100644 --- a/.github/scripts/test-autoplay.js +++ b/.github/scripts/test-autoplay.js @@ -72,6 +72,17 @@ async function waitFor(page, predicate, { timeout = 5000, interval = 100 } = {}) throw new Error(`expected the cookie banner to be visible alongside ${overlayType}, but it wasn't`); } + // Let the muted autoplay run for a few seconds before clicking, so + // currentTime has clearly moved past the intro - this is the whole + // point of the check below: a real visitor rarely clicks instantly, + // and the payoff should still start from the beginning regardless of + // how long the decoy held their attention. + await page.waitForTimeout(3000); + const beforeClickTime = await page.evaluate(() => document.getElementById('video').currentTime); + if (beforeClickTime < 1) { + throw new Error(`expected currentTime to have advanced past 1s after 3s of muted autoplay, got ${beforeClickTime}`); + } + await page.click(clickSelector); const unmuted = await waitFor(page, () => { @@ -88,6 +99,7 @@ async function waitFor(page, predicate, { timeout = 5000, interval = 100 } = {}) const cookie = document.querySelector(cookieSel); return { paused: v.paused, + currentTime: v.currentTime, title: document.title, overlayVisible: overlay ? !overlay.classList.contains('hidden') : null, cookieVisible: cookie ? !cookie.classList.contains('hidden') : null @@ -97,6 +109,11 @@ async function waitFor(page, predicate, { timeout = 5000, interval = 100 } = {}) if (after.paused) { throw new Error('expected video to still be playing after unmuting'); } + // The whole point of a rickroll is the intro - a visitor who takes a + // while to click shouldn't hear the song mid-way through instead. + if (after.currentTime >= 1) { + throw new Error(`expected video to restart from the beginning on reveal, but currentTime was ${after.currentTime}s right after clicking (was ${beforeClickTime}s before)`); + } if (after.title !== 'Rickroll') { throw new Error(`expected title "Rickroll" after interaction, got "${after.title}"`); } diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 82a34bb..aa55ef8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,6 +34,11 @@ jobs: file: ./Dockerfile load: true tags: rickroll:test + # Same video-fetch stage as ghcr-publish.yml, and this + # workflow always builds the same default VIDEO_URL - caching + # it means CI stops re-downloading the video on every run. + cache-from: type=gha,scope=test + cache-to: type=gha,mode=max,scope=test - name: Start container with default env vars run: docker run -d --name rickroll-default -p 8080:8080 rickroll:test @@ -157,15 +162,32 @@ jobs: with: node-version: 24 - - name: Install Playwright + - name: Install Playwright package run: | npm init -y >/dev/null - npm install --no-save playwright@1 - # Firefox gets its own engine check alongside Chromium - it's - # historically the most divergent on autoplay/gesture policy - # enforcement of the mainstream browsers, so a bug that's fine - # under one can still be broken under the other. - npx playwright install --with-deps chromium firefox + # Pinned exact version, not a floating "playwright@1" - the + # cache key below is keyed on this version, and an unpinned + # install could silently resolve a newer release whose browser + # binaries no longer match a stale cache. + npm install --no-save playwright@1.61.1 + + - name: Cache Playwright browsers + id: playwright-cache + uses: actions/cache@v4 + with: + path: ~/.cache/ms-playwright + key: playwright-browsers-${{ runner.os }}-1.61.1 + + - name: Install Playwright OS dependencies + run: npx playwright install-deps chromium firefox + + - name: Install Playwright browsers + if: steps.playwright-cache.outputs.cache-hit != 'true' + # Firefox gets its own engine check alongside Chromium - it's + # historically the most divergent on autoplay/gesture policy + # enforcement of the mainstream browsers, so a bug that's fine + # under one can still be broken under the other. + run: npx playwright install chromium firefox - name: Start container with OVERLAY=error run: docker run -d --name rickroll-error -p 8080:8080 -e OVERLAY=error rickroll:test diff --git a/scripts/index/80-index.sh b/scripts/index/80-index.sh index 4987921..45a02ed 100644 --- a/scripts/index/80-index.sh +++ b/scripts/index/80-index.sh @@ -348,6 +348,13 @@ tee /usr/share/nginx/html/index.html << EOF >/dev/null var events = ['click', 'keydown']; function reveal() { + // The video's been playing muted since page load, so by + // the time the decoy actually gets clicked - anywhere + // from a second to minutes later - it's already well + // past the intro. Jump back to the start so the payoff + // always lands on the actual song opening, not wherever + // playback happened to be. + video.currentTime = 0; video.muted = false; video.play().catch(function () {}); document.title = title;