6daf3237e8
Players can save up to 3 favorite opening words and tap one to fill the current row. Words are stored on profiles.starter_words and saved through the set_starter_words RPC, which validates them against the dictionary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
164 lines
5.7 KiB
JavaScript
164 lines
5.7 KiB
JavaScript
const { test, expect } = require("@playwright/test")
|
|
|
|
async function openApp(page) {
|
|
await page.goto("/index.html")
|
|
await page.evaluate(() => {
|
|
localStorage.clear()
|
|
sessionStorage.clear()
|
|
})
|
|
await page.goto("/index.html")
|
|
}
|
|
|
|
async function skipIntro(page) {
|
|
await expect(page.locator("#intro-overlay")).toBeVisible()
|
|
await page.locator("#intro-skip").click()
|
|
await expect(page.locator("#intro-overlay")).toBeHidden()
|
|
await expect(page.locator("[data-guess-grid] .tile")).toHaveCount(30)
|
|
await expect(page.locator("[data-key]")).toHaveCount(26)
|
|
}
|
|
|
|
async function expectGameReadyWithoutIntro(page) {
|
|
await expect(page.locator("#intro-overlay")).toBeHidden()
|
|
await expect(page.locator("[data-guess-grid] .tile")).toHaveCount(30)
|
|
await expect(page.locator("[data-key]")).toHaveCount(26)
|
|
}
|
|
|
|
test("loads the intro and reveals the game board", async ({ page }) => {
|
|
await openApp(page)
|
|
|
|
await expect(page.locator("#intro-overlay")).toBeVisible()
|
|
await expect(page.locator("#intro-tiles .intro-tile")).toHaveCount(6)
|
|
await skipIntro(page)
|
|
await expect(page.locator("#round-status")).not.toContainText("Loading")
|
|
})
|
|
|
|
test("uses dark mode by default and preserves a light preference", async ({ page }) => {
|
|
await openApp(page)
|
|
await skipIntro(page)
|
|
|
|
await expect(page.locator("body")).toHaveClass(/dark-theme/)
|
|
await page.locator("#theme-button").click()
|
|
await expect(page.locator("body")).not.toHaveClass(/dark-theme/)
|
|
await expect(page.locator("#theme-button")).toHaveAttribute("aria-label", "Switch to dark mode")
|
|
|
|
await page.reload()
|
|
await expectGameReadyWithoutIntro(page)
|
|
await expect(page.locator("body")).not.toHaveClass(/dark-theme/)
|
|
})
|
|
|
|
test("does not replay the intro after a same-tab reload", async ({ page }) => {
|
|
await openApp(page)
|
|
await skipIntro(page)
|
|
|
|
await page.reload()
|
|
await expectGameReadyWithoutIntro(page)
|
|
})
|
|
|
|
test("pressing Enter after changing theme does not re-trigger the theme button", async ({ page }) => {
|
|
await openApp(page)
|
|
await skipIntro(page)
|
|
|
|
await page.locator("#theme-button").click()
|
|
await expect(page.locator("body")).not.toHaveClass(/dark-theme/)
|
|
|
|
await page.keyboard.press("Enter")
|
|
await expect(page.locator("body")).not.toHaveClass(/dark-theme/)
|
|
})
|
|
|
|
test("keyboard input fills and deletes board tiles", async ({ page }) => {
|
|
await openApp(page)
|
|
await skipIntro(page)
|
|
|
|
const firstTile = page.locator("[data-guess-grid] .tile").first()
|
|
await page.keyboard.press("A")
|
|
await expect(firstTile).toHaveText("a")
|
|
await expect(firstTile).toHaveAttribute("data-state", "active")
|
|
|
|
await page.keyboard.press("Backspace")
|
|
await expect(firstTile).toHaveText("")
|
|
await expect(firstTile).not.toHaveAttribute("data-letter", /./)
|
|
})
|
|
|
|
test("incomplete guesses show feedback without changing theme", async ({ page }) => {
|
|
await openApp(page)
|
|
await skipIntro(page)
|
|
|
|
await page.keyboard.press("Enter")
|
|
await expect(page.locator("[data-alert-container]")).toContainText("Not enough letters")
|
|
await expect(page.locator("body")).toHaveClass(/dark-theme/)
|
|
})
|
|
|
|
test("stats and leaderboard modals open", async ({ page }) => {
|
|
await openApp(page)
|
|
await skipIntro(page)
|
|
|
|
await page.locator("#Stats-button").click()
|
|
await expect(page.locator("#stats-modal")).toBeVisible()
|
|
await page.locator("#stats-modal .close-button").click()
|
|
await expect(page.locator("#stats-modal")).toBeHidden()
|
|
|
|
await page.locator("#leaderboard-button").click()
|
|
await expect(page.locator("#leaderboard-modal")).toBeVisible()
|
|
})
|
|
|
|
test("definition lookup leaves loading state when a provider times out", async ({ page }) => {
|
|
await page.route("https://api.dictionaryapi.dev/**", () => new Promise(() => {}))
|
|
await page.route("https://en.wiktionary.org/**", route => route.abort())
|
|
await openApp(page)
|
|
await skipIntro(page)
|
|
|
|
await page.evaluate(() => {
|
|
lastResult = { won: true, guesses: 1, word: "apple" }
|
|
loadWordDefinition("apple", true)
|
|
})
|
|
|
|
await expect(page.locator("#stats-definition")).toContainText("Definition not available at the moment.", {
|
|
timeout: 6500
|
|
})
|
|
})
|
|
|
|
test("guests are prompted to sign in to save starter words", async ({ page }) => {
|
|
await openApp(page)
|
|
await skipIntro(page)
|
|
|
|
const starterPanel = page.locator("#starter-panel")
|
|
await expect(starterPanel).toBeVisible()
|
|
await expect(starterPanel).toContainText("to save up to 3 favorite openers")
|
|
await expect(page.locator("#starter-edit")).toBeHidden()
|
|
|
|
await starterPanel.locator("[data-starter-sign-in]").click()
|
|
await expect(page.locator("#auth-modal")).toBeVisible()
|
|
})
|
|
|
|
test("starter words fill the current row and the editor keeps keys off the board", async ({ page }) => {
|
|
await page.route("**/rest/v1/rpc/set_starter_words", route => route.fulfill({ json: ["crane", "arise"] }))
|
|
await openApp(page)
|
|
await skipIntro(page)
|
|
await expect(page.locator("#round-status")).not.toContainText("Loading")
|
|
|
|
await page.evaluate(() => {
|
|
authSession = { user: { id: "test-user", email: "player@example.test" } }
|
|
starterWords = ["plane", "arise"]
|
|
starterWordsLoaded = true
|
|
renderStarterPanel()
|
|
})
|
|
|
|
const readFirstRow = () => page.locator("[data-guess-grid] .tile").evaluateAll(tiles =>
|
|
tiles.slice(0, 5).map(tile => tile.dataset.letter || "").join("")
|
|
)
|
|
|
|
await page.keyboard.press("Q")
|
|
await page.locator("[data-starter-word='arise']").click()
|
|
expect(await readFirstRow()).toBe("arise")
|
|
|
|
await page.locator("#starter-edit").click()
|
|
const firstInput = page.locator(".starter-input").first()
|
|
await expect(firstInput).toHaveValue("plane")
|
|
await firstInput.fill("crane")
|
|
await firstInput.press("Enter")
|
|
|
|
await expect(page.locator("[data-alert-container]")).toContainText("Starter words saved")
|
|
await expect(page.locator("[data-starter-word]")).toHaveText(["crane", "arise"])
|
|
expect(await readFirstRow()).toBe("arise")
|
|
})
|