6e85917f73
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.
192 lines
7.2 KiB
JavaScript
192 lines
7.2 KiB
JavaScript
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()
|