Files
ZabavnaMatematika/web/scripts/smoke.mjs
T
klevze 6e85917f73 Initial commit: Zabavna Matematika web and Android app.
Kids math practice (SL/EN) in a Vite React app, wrapped with Capacitor for Android (net.bit76.matematika). Includes session refactor, i18n, and text-free difficulty icons with translated overlays.
2026-08-15 10:59:25 +02:00

192 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { chromium } from 'playwright'
function evalEq(text) {
const m = String(text || '').match(/(-?\d+)\s*([+\-×÷])\s*(-?\d+)/)
if (!m) return null
const a = Number(m[1])
const b = Number(m[3])
const op = m[2]
if (op === '+') return a + b
if (op === '-') return a - b
if (op === '×') return a * b
if (op === '÷') return a / b
return null
}
const browser = await chromium.launch({
headless: true,
channel: process.env.PW_CHANNEL || 'msedge'
})
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } })
await page.addInitScript(() => {
localStorage.setItem('matematika_lang', 'sl')
localStorage.setItem('matematika_settings', JSON.stringify({
name: 'Test',
stevilo_racunov: 2,
max_number: 20,
sestevanje: true,
odstevanje: false,
mnozenje: false,
deljenje: false,
za_mnozenje: [2],
za_deljenje: [2],
difficulty: 'easy'
}))
})
const url = process.env.SMOKE_URL || 'http://127.0.0.1:4173/'
await page.goto(url, { waitUntil: 'domcontentloaded' })
const continueBtn = page.getByRole('button', { name: 'Nadaljuj' })
await continueBtn.waitFor({ state: 'visible', timeout: 120000 })
await page.waitForFunction(() => {
const btn = [...document.querySelectorAll('button')].find((b) => b.textContent.trim() === 'Nadaljuj')
return btn && !btn.disabled
}, null, { timeout: 120000 })
await continueBtn.click()
await page.getByRole('button', { name: 'IGRAJ' }).first().click()
await page.getByRole('button', { name: 'IGRAJ' }).last().click()
await page.locator('.answer-tile').first().waitFor({ timeout: 15000 })
async function currentEq() {
return page.locator('.font-extrabold.leading-tight').innerText()
}
async function clickTile(expected) {
const tiles = page.locator('.answer-tile')
const n = await tiles.count()
for (let i = 0; i < n; i += 1) {
const label = (await tiles.nth(i).innerText()).replace(/[^\d-]/g, '')
if (Number(label) === expected) {
await tiles.nth(i).click()
return
}
}
throw new Error(`No tile for ${expected}`)
}
async function answerCurrent({ wrongFirst = false } = {}) {
const eq = await currentEq()
const expected = evalEq(eq)
if (expected === null) throw new Error(`Could not parse equation: ${eq}`)
if (wrongFirst) {
const tiles = page.locator('.answer-tile:not([disabled])')
const n = await tiles.count()
for (let i = 0; i < n; i += 1) {
const label = (await tiles.nth(i).innerText()).replace(/[^\d-]/g, '')
if (Number(label) !== expected) {
await tiles.nth(i).click()
await page.getByText(/Narobe/i).first().waitFor({ timeout: 3000 })
await page.waitForTimeout(400)
break
}
}
}
await clickTile(expected)
return eq
}
async function exitToSettings() {
await page.getByRole('button', { name: 'Izhod' }).click()
await page.locator('.fixed.inset-0').getByRole('button', { name: 'Izhod' }).click()
await page.getByRole('button', { name: 'IGRAJ' }).first().waitFor({ timeout: 8000 })
}
async function startFromSettings() {
await page.getByRole('button', { name: 'IGRAJ' }).first().click()
await page.getByRole('button', { name: 'IGRAJ' }).last().click()
}
const first = await answerCurrent()
await page.getByText(/Bravo/i).first().waitFor({ timeout: 5000 })
await page.waitForTimeout(2400)
const second = await answerCurrent()
await page.getByRole('button', { name: 'Začni ponovno' }).waitFor({ timeout: 8000 })
await page.getByRole('button', { name: 'Začni ponovno' }).click()
await page.locator('.answer-tile').first().waitFor({ timeout: 8000 })
const afterRestart = await currentEq()
if (!/[=+]/.test(afterRestart)) {
throw new Error(`Restart did not show a new question: ${afterRestart}`)
}
if (await page.getByRole('button', { name: 'Začni ponovno' }).isVisible().catch(() => false)) {
throw new Error('Restart immediately showed game over')
}
const easyTiles = await page.locator('.answer-tile').count()
if (easyTiles !== 4) throw new Error(`Easy should have 4 tiles, got ${easyTiles}`)
await exitToSettings()
await page.getByRole('button', { name: 'Srednje' }).click()
await startFromSettings()
await page.locator('.answer-tile').first().waitFor({ timeout: 15000 })
const mediumTiles = await page.locator('.answer-tile').count()
if (mediumTiles !== 6) throw new Error(`Medium should have 6 tiles, got ${mediumTiles}`)
await answerCurrent({ wrongFirst: true })
await page.getByText(/Bravo/i).first().waitFor({ timeout: 5000 })
await page.waitForTimeout(2400)
await answerCurrent()
await page.getByRole('button', { name: 'Začni ponovno' }).waitFor({ timeout: 8000 })
await page.getByRole('button', { name: 'Nastavitve' }).click()
await page.getByRole('button', { name: 'IGRAJ' }).first().waitFor({ timeout: 8000 })
await page.getByRole('button', { name: 'Težko' }).click()
await startFromSettings()
await page.getByRole('button', { name: 'OK' }).waitFor({ timeout: 15000 })
if (await page.locator('.answer-tile').count()) {
throw new Error('Hard should not show choice tiles')
}
if (await page.getByRole('button', { name: '±' }).count()) {
throw new Error('± should be hidden when negatives are off')
}
if (await page.getByRole('button', { name: 'OK' }).isEnabled()) {
throw new Error('OK should stay disabled on empty keypad')
}
const hardEq = await currentEq()
const hardExpected = evalEq(hardEq)
if (hardExpected === null) throw new Error(`Could not parse hard equation: ${hardEq}`)
const digits = String(Math.abs(hardExpected))
for (const d of digits) {
await page.getByRole('button', { name: d, exact: true }).click()
}
if (!(await page.getByRole('button', { name: 'OK' }).isEnabled())) {
throw new Error('OK should enable after entering digits')
}
await page.getByRole('button', { name: 'OK' }).click()
await page.getByText(/Bravo/i).first().waitFor({ timeout: 5000 })
await page.waitForTimeout(2400)
const hardEq2 = await currentEq()
const hardExpected2 = evalEq(hardEq2)
if (hardExpected2 === null) throw new Error(`Could not parse hard equation 2: ${hardEq2}`)
for (const d of String(Math.abs(hardExpected2))) {
await page.getByRole('button', { name: d, exact: true }).click()
}
await page.getByRole('button', { name: 'OK' }).click()
await page.getByRole('button', { name: 'Nastavitve' }).waitFor({ timeout: 8000 })
await page.setViewportSize({ width: 390, height: 844 })
await page.getByRole('button', { name: 'Nastavitve' }).click()
await page.getByRole('button', { name: 'Lahko' }).click()
await startFromSettings()
await page.locator('.answer-tile').first().waitFor({ timeout: 15000 })
const mobileTiles = await page.locator('.answer-tile').count()
if (mobileTiles !== 4) throw new Error(`Mobile easy should have 4 tiles, got ${mobileTiles}`)
const tileBox = await page.locator('.answer-tile').first().boundingBox()
if (!tileBox || tileBox.width < 80 || tileBox.height < 40) {
throw new Error(`Mobile tiles too small: ${JSON.stringify(tileBox)}`)
}
if (tileBox.x + tileBox.width > 390) {
throw new Error('Mobile tile overflows the viewport')
}
const eqBox = await page.locator('.font-extrabold.leading-tight').boundingBox()
if (eqBox && eqBox.x + eqBox.width > 390) {
throw new Error('Equation overflows the mobile viewport')
}
console.log('SMOKE_OK', { first, second, afterRestart, easyTiles, mediumTiles, hardEq, mobileTiles, tileBox })
await browser.close()