| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- /**
- * Playwright E2E tests for mini-games scoring flows
- * Run: npx playwright test tests/e2e/mini-games-playwright.spec.js
- */
- const { test, expect } = require('@playwright/test');
- test.describe('Mini-Games E2E - Playwright', () => {
-
- test.beforeEach(async ({ page }) => {
- // Navigate to the mini-games list page (uni-app H5 hash router)
- // Requires `npm run dev:h5` running in cfc-frontend
- await page.goto('/#/pages/games/list');
-
- // Wait for page to load
- await page.waitForLoadState('networkidle');
- });
- test('1a2b - should display game and start correctly', async ({ page }) => {
- // Navigate to 1a2b game
- await page.click('text=猜数字');
-
- // Wait for game page to load
- await page.waitForSelector('.game-container');
-
- // Verify game title
- await expect(page.locator('.game-title')).toContainText('猜数字');
-
- // Verify initial state
- await expect(page.locator('.instructions')).toBeVisible();
- });
- test('1a2b - should calculate and display score on win', async ({ page }) => {
- await page.goto('/#/pages/games/1a2b');
-
- // Mock the API response
- await page.route('**/api/mini-game/complete', async route => {
- await route.fulfill({
- status: 200,
- body: JSON.stringify({
- code: 200,
- data: {
- pointsEarned: 10,
- newBalance: 110
- }
- })
- });
- });
-
- // Start game with input
- await page.fill('input[placeholder="输入4位不重复数字"]', '1234');
- await page.click('button:has-text("开始游戏")');
-
- // If first guess is correct (lucky), verify result modal
- // Note: In real scenario, we'd need to mock the answer generation
- const resultModal = page.locator('.result-modal');
- if (await resultModal.isVisible()) {
- await expect(resultModal.locator('.result-score')).toContainText('积分');
- }
- });
- test('1a2b - should handle missing childId gracefully', async ({ page }) => {
- await page.goto('/#/pages/games/1a2b');
-
- // Clear any stored user data
- await page.evaluate(() => {
- uni.removeStorageSync('userId');
- uni.removeStorageSync('token');
- });
-
- // Start game
- await page.fill('input[placeholder="输入4位不重复数字"]', '1234');
- await page.click('button:has-text("开始游戏")');
-
- // Game should still work, but score submission should be skipped
- // No error should be thrown
- await page.waitForSelector('.game-container', { state: 'visible' });
- });
- test('Sudoku - should display difficulty selection', async ({ page }) => {
- await page.goto('/#/pages/games/sudoku');
-
- // Verify difficulty buttons
- await expect(page.locator('.level-btn:has-text("简单")')).toBeVisible();
- await expect(page.locator('.level-btn:has-text("中等")')).toBeVisible();
- await expect(page.locator('.level-btn:has-text("困难")')).toBeVisible();
- });
- test('Sudoku - should start game and display board', async ({ page }) => {
- await page.goto('/#/pages/games/sudoku');
-
- // Select difficulty
- await page.click('.level-btn:has-text("简单")');
-
- // Start game
- await page.click('button:has-text("开始游戏")');
-
- // Verify board is displayed
- await expect(page.locator('.board')).toBeVisible();
-
- // Verify number pad
- await expect(page.locator('.number-pad')).toBeVisible();
- });
- test('Sudoku - should calculate score based on difficulty and time', async ({ page }) => {
- await page.goto('/#/pages/games/sudoku');
-
- // Mock API
- await page.route('**/api/mini-game/complete', async route => {
- await route.fulfill({
- status: 200,
- body: JSON.stringify({
- code: 200,
- data: {
- pointsEarned: 12,
- newBalance: 112
- }
- })
- });
- });
-
- // Select hard difficulty
- await page.click('.level-btn:has-text("困难")');
- await page.click('button:has-text("开始游戏")');
-
- // Simulate completion (in real test, would need to solve the puzzle)
- // This is a placeholder for actual game completion logic
- const board = page.locator('.board');
- await expect(board).toBeVisible();
- });
- test('API - should call completeMiniGame with correct params', async ({ page }) => {
- await page.goto('/#/pages/games/schulte');
-
- // Intercept API call
- let apiCallParams = null;
- await page.route('**/api/mini-game/complete', async (route, request) => {
- apiCallParams = request.postDataJSON();
- await route.fulfill({
- status: 200,
- body: JSON.stringify({
- code: 200,
- data: { pointsEarned: 8, newBalance: 108 }
- })
- });
- });
-
- // Start and complete Schulte game
- await page.click('button:has-text("开始游戏")');
-
- // Click cells in order (1-25)
- // Note: This would need actual cell clicking logic
- // For now, we'll just verify the API would be called with correct params
-
- // If game completes, verify API call
- if (apiCallParams) {
- expect(apiCallParams.childId).toBeDefined();
- expect(apiCallParams.gameCode).toBe('schulte');
- expect(apiCallParams.completionTime).toBeGreaterThanOrEqual(0);
- expect(apiCallParams.score).toBeGreaterThanOrEqual(60);
- }
- });
- test('UI - should display result modal with score', async ({ page }) => {
- await page.goto('/#/pages/games/schulte');
-
- // Mock a completed game state
- await page.evaluate(() => {
- // This would set the game state to show results
- // In a real test, we'd complete the actual game
- window.testGameComplete = true;
- });
-
- // If result modal is shown, verify it displays score
- const resultModal = page.locator('.result-modal');
- if (await resultModal.isVisible({ timeout: 5000 }).catch(() => false)) {
- await expect(resultModal.locator('.result-title')).toBeVisible();
- await expect(resultModal.locator('.result-score')).toContainText('积分');
- await expect(resultModal.locator('button:has-text("再来")')).toBeVisible();
- }
- });
- test('Games List - should navigate to all games', async ({ page }) => {
- await page.goto('/#/pages/games/list');
-
- // Verify all game cards are visible
- await expect(page.locator('text=舒尔特方格')).toBeVisible();
- await expect(page.locator('text=猜数字')).toBeVisible();
- await expect(page.locator('text=数独')).toBeVisible();
-
- // Click and verify navigation
- await page.click('text=舒尔特方格');
- await expect(page).toHaveURL(/.*schulte/);
-
- await page.goBack();
-
- await page.click('text=数独');
- await expect(page).toHaveURL(/.*sudoku/);
- });
- });
- // Integration test for score calculation logic
- test.describe('Score Calculation Logic', () => {
-
- test('1a2b score calculation', () => {
- const calculateScore = (guessesUsed) => Math.max(100 - guessesUsed * 8, 60);
-
- // Test various scenarios
- expect(calculateScore(1)).toBe(92);
- expect(calculateScore(2)).toBe(84);
- expect(calculateScore(5)).toBe(60); // Minimum
- expect(calculateScore(10)).toBe(60);
- });
- test('Sudoku score calculation', () => {
- const calculateSudokuScore = (difficulty, time) => {
- const baseScore = { easy: 60, medium: 75, hard: 90 };
- const base = baseScore[difficulty] || 60;
- const timeBonus = Math.max(40 - Math.floor(time / 10), 0);
- return base + timeBonus;
- };
-
- // Test various scenarios
- expect(calculateSudokuScore('easy', 30)).toBe(97);
- expect(calculateSudokuScore('medium', 60)).toBe(109);
- expect(calculateSudokuScore('hard', 120)).toBe(118);
- expect(calculateSudokuScore('easy', 500)).toBe(60); // No time bonus
- });
- test('Schulte score calculation', () => {
- const calculateSchulteScore = (time) => Math.max(100 - Math.floor(time * 2), 60);
-
- expect(calculateSchulteScore(10)).toBe(80);
- expect(calculateSchulteScore(20)).toBe(60); // Minimum
- expect(calculateSchulteScore(30)).toBe(60);
- });
- });
|