first commit
This commit is contained in:
182
tests/browser.mjs
Normal file
182
tests/browser.mjs
Normal file
@@ -0,0 +1,182 @@
|
||||
import { chromium } from '@playwright/test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdir } from 'node:fs/promises';
|
||||
await mkdir('artifacts', { recursive: true });
|
||||
const browser = await chromium.launch({
|
||||
executablePath:
|
||||
process.env.CHROME_PATH ||
|
||||
(process.platform === 'darwin'
|
||||
? '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
|
||||
: undefined),
|
||||
headless: true,
|
||||
args: ['--no-sandbox', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'],
|
||||
});
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
|
||||
const errors = [];
|
||||
page.on('pageerror', (e) => {
|
||||
errors.push(String(e));
|
||||
console.log('PAGE ERROR', String(e));
|
||||
});
|
||||
page.on('console', (m) => {
|
||||
if (m.type() === 'error') console.log('CONSOLE ERROR', m.text());
|
||||
});
|
||||
await page.goto('http://127.0.0.1:5173/');
|
||||
try {
|
||||
await page.getByRole('button', { name: '接受挑战' }).waitFor({ timeout: 10000 });
|
||||
} catch (e) {
|
||||
await page.screenshot({ path: 'artifacts/startup-error.png' });
|
||||
console.log(await page.locator('body').innerText());
|
||||
await browser.close();
|
||||
throw e;
|
||||
}
|
||||
await page.screenshot({ path: 'artifacts/blind-selection.png' });
|
||||
await page.getByRole('button', { name: '接受挑战' }).click();
|
||||
await page.keyboard.press('1');
|
||||
await page.keyboard.press('2');
|
||||
assert.equal(await page.locator('.sr-only button[aria-pressed=true]').count(), 2);
|
||||
await page.screenshot({ path: 'artifacts/playing.png' });
|
||||
await page.keyboard.press('d');
|
||||
assert.equal(await page.locator('.counters strong').nth(1).textContent(), '2');
|
||||
const before = await page.evaluate(() => JSON.parse(localStorage.getItem('velvet-joker-run-v1')));
|
||||
await page.reload();
|
||||
await page.getByRole('button', { name: '出牌', exact: false }).waitFor();
|
||||
const after = await page.evaluate(() => JSON.parse(localStorage.getItem('velvet-joker-run-v1')));
|
||||
assert.deepEqual(after, before);
|
||||
await page.mouse.move(474, 665);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(1120, 665, { steps: 15 });
|
||||
await page.mouse.up();
|
||||
const moved = await page.evaluate(
|
||||
() => JSON.parse(localStorage.getItem('velvet-joker-run-v1')).hand,
|
||||
);
|
||||
if (JSON.stringify(moved) === JSON.stringify(before.hand)) {
|
||||
await page.screenshot({ path: 'artifacts/drag-error.png' });
|
||||
console.log('DRAG MODAL', await page.locator('#modal').innerText());
|
||||
}
|
||||
assert.notDeepEqual(moved, before.hand);
|
||||
await page.keyboard.press('1');
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForFunction(() => !document.querySelector('#scoring-source'));
|
||||
assert.equal(await page.locator('.counters strong').first().textContent(), '3');
|
||||
await page.getByRole('button', { name: '牌型图鉴' }).click();
|
||||
assert.equal(await page.locator('.hand-table tbody tr').count(), 12);
|
||||
await page.keyboard.press('Escape');
|
||||
// Deterministic fixtures exercise full UI flows; they do not assert natural win rate.
|
||||
await page.evaluate(async () => {
|
||||
const { createRun, dispatch } = await import('/src/core/run.ts');
|
||||
let r = createRun('BROWSER');
|
||||
r = dispatch(r, { type: 'enter' }).state;
|
||||
r.cards.slice(0, 5).forEach((c, i) => {
|
||||
c.rank = i === 4 ? 9 : 13;
|
||||
});
|
||||
r.hand = r.cards.slice(0, 8).map((c) => c.id);
|
||||
r.draw = r.cards.slice(8).map((c) => c.id);
|
||||
r.money = 30;
|
||||
r.target = 300;
|
||||
r.jokers = [
|
||||
{ uid: 200, id: 'smile', value: 0 },
|
||||
{ uid: 201, id: 'pair', value: 0 },
|
||||
{ uid: 202, id: 'double', value: 0 },
|
||||
];
|
||||
r.consumables = [{ uid: 203, kind: 'planet', ref: 1, price: 3 }];
|
||||
r.nextId = 300;
|
||||
localStorage.setItem('velvet-joker-run-v1', JSON.stringify(r));
|
||||
localStorage.setItem('velvet-speed', '4');
|
||||
});
|
||||
await page.reload();
|
||||
await page.getByRole('button', { name: '出牌', exact: false }).waitFor();
|
||||
await page.mouse.click(421, 240);
|
||||
await page.getByRole('button', { name: '右移' }).click();
|
||||
assert.equal(
|
||||
await page.evaluate(() => JSON.parse(localStorage.getItem('velvet-joker-run-v1')).jokers[0].id),
|
||||
'pair',
|
||||
);
|
||||
await page.waitForFunction(() => !document.querySelector('.dialog'));
|
||||
await page.screenshot({ path: 'artifacts/before-consumable.png' });
|
||||
await page.mouse.click(1232, 228);
|
||||
try {
|
||||
await page.getByRole('button', { name: '使用', exact: true }).click({ timeout: 6000 });
|
||||
} catch (e) {
|
||||
await page.screenshot({ path: 'artifacts/consumable-error.png' });
|
||||
console.log('DOM', await page.locator('#modal').innerText());
|
||||
await browser.close();
|
||||
throw e;
|
||||
}
|
||||
assert.equal(
|
||||
await page.evaluate(() => JSON.parse(localStorage.getItem('velvet-joker-run-v1')).levels[1]),
|
||||
2,
|
||||
);
|
||||
await page.keyboard.press('1');
|
||||
await page.keyboard.press('2');
|
||||
await page.screenshot({ path: 'artifacts/joker-table.png' });
|
||||
await page.keyboard.press('Enter');
|
||||
await page.getByRole('button', { name: '前往商店' }).waitFor({ timeout: 10000 });
|
||||
const paid = await page.evaluate(
|
||||
() => JSON.parse(localStorage.getItem('velvet-joker-run-v1')).money,
|
||||
);
|
||||
await page.reload();
|
||||
await page.getByRole('button', { name: '前往商店' }).click();
|
||||
assert.equal(
|
||||
await page.evaluate(() => JSON.parse(localStorage.getItem('velvet-joker-run-v1')).money),
|
||||
paid,
|
||||
);
|
||||
await page.screenshot({ path: 'artifacts/shop.png' });
|
||||
await page.locator('[data-action=buy]').first().click();
|
||||
await page.locator('[data-action=reroll]').click();
|
||||
await page.locator('[data-action=buy]').last().click();
|
||||
await page.getByRole('button', { name: '选择此牌' }).first().click();
|
||||
await page.getByRole('button', { name: '下一盲注' }).click();
|
||||
await page.getByRole('button', { name: '跳过' }).click();
|
||||
assert.match(await page.locator('.blind-head').textContent(), /BOSS/);
|
||||
await page.setViewportSize({ width: 1280, height: 720 });
|
||||
await page.screenshot({ path: 'artifacts/desktop-1280.png' });
|
||||
await page.setViewportSize({ width: 932, height: 430 });
|
||||
await page.screenshot({ path: 'artifacts/mobile-landscape.png' });
|
||||
await page.getByRole('button', { name: '接受挑战' }).click();
|
||||
assert.equal(await page.locator('canvas').count(), 1);
|
||||
assert.deepEqual(errors, []);
|
||||
const mobile = await browser.newPage({
|
||||
viewport: { width: 932, height: 430 },
|
||||
hasTouch: true,
|
||||
isMobile: true,
|
||||
});
|
||||
mobile.on('pageerror', (e) => errors.push(String(e)));
|
||||
await mobile.goto('http://127.0.0.1:5173/');
|
||||
await mobile.getByRole('button', { name: '接受挑战' }).click();
|
||||
const bounds = await mobile.locator('canvas').boundingBox();
|
||||
const point = (x, y) => ({
|
||||
x: bounds.x + (x * bounds.width) / 1440,
|
||||
y: bounds.y + (y * bounds.height) / 900,
|
||||
});
|
||||
await mobile.touchscreen.tap(point(474, 665).x, point(474, 665).y);
|
||||
assert.equal(await mobile.locator('.sr-only button[aria-pressed=true]').count(), 1);
|
||||
const touch = await mobile.context().newCDPSession(mobile);
|
||||
await touch.send('Input.dispatchTouchEvent', {
|
||||
type: 'touchStart',
|
||||
touchPoints: [point(573, 665)],
|
||||
});
|
||||
await mobile.getByRole('dialog').waitFor({ timeout: 4000 });
|
||||
await touch.send('Input.dispatchTouchEvent', { type: 'touchEnd', touchPoints: [] });
|
||||
assert.match(await mobile.locator('.dialog').innerText(), /基础筹码/);
|
||||
await mobile.getByRole('button', { name: '关闭', exact: true }).click();
|
||||
const mobileBefore = await mobile.evaluate(
|
||||
() => JSON.parse(localStorage.getItem('velvet-joker-run-v1')).hand,
|
||||
);
|
||||
await touch.send('Input.dispatchTouchEvent', {
|
||||
type: 'touchStart',
|
||||
touchPoints: [point(474, 665)],
|
||||
});
|
||||
await touch.send('Input.dispatchTouchEvent', {
|
||||
type: 'touchMove',
|
||||
touchPoints: [point(1100, 665)],
|
||||
});
|
||||
await touch.send('Input.dispatchTouchEvent', { type: 'touchEnd', touchPoints: [] });
|
||||
assert.notDeepEqual(
|
||||
await mobile.evaluate(() => JSON.parse(localStorage.getItem('velvet-joker-run-v1')).hand),
|
||||
mobileBefore,
|
||||
);
|
||||
assert.deepEqual(errors, []);
|
||||
console.log(
|
||||
'Browser smoke passed: Canvas selection/drag, play/discard, reload, joker order, consumable, reward, shop, pack, skip, responsive sizes, emulated touch tap/long-press/drag.',
|
||||
);
|
||||
await browser.close();
|
||||
Reference in New Issue
Block a user