mini-games-playwright.spec.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /**
  2. * Playwright E2E tests for mini-games scoring flows
  3. * Run: npx playwright test tests/e2e/mini-games-playwright.spec.js
  4. */
  5. const { test, expect } = require('@playwright/test');
  6. test.describe('Mini-Games E2E - Playwright', () => {
  7. test.beforeEach(async ({ page }) => {
  8. // Navigate to the mini-games list page
  9. // Note: Adjust URL based on your actual deployment
  10. await page.goto('/pages/games/list');
  11. // Wait for page to load
  12. await page.waitForLoadState('networkidle');
  13. });
  14. test('1a2b - should display game and start correctly', async ({ page }) => {
  15. // Navigate to 1a2b game
  16. await page.click('text=猜数字');
  17. // Wait for game page to load
  18. await page.waitForSelector('.game-container');
  19. // Verify game title
  20. await expect(page.locator('.game-title')).toContainText('猜数字');
  21. // Verify initial state
  22. await expect(page.locator('.instructions')).toBeVisible();
  23. });
  24. test('1a2b - should calculate and display score on win', async ({ page }) => {
  25. await page.goto('/pages/games/1a2b');
  26. // Mock the API response
  27. await page.route('**/api/mini-game/complete', async route => {
  28. await route.fulfill({
  29. status: 200,
  30. body: JSON.stringify({
  31. code: 200,
  32. data: {
  33. pointsEarned: 10,
  34. newBalance: 110
  35. }
  36. })
  37. });
  38. });
  39. // Start game with input
  40. await page.fill('input[placeholder="输入4位不重复数字"]', '1234');
  41. await page.click('button:has-text("开始游戏")');
  42. // If first guess is correct (lucky), verify result modal
  43. // Note: In real scenario, we'd need to mock the answer generation
  44. const resultModal = page.locator('.result-modal');
  45. if (await resultModal.isVisible()) {
  46. await expect(resultModal.locator('.result-score')).toContainText('积分');
  47. }
  48. });
  49. test('1a2b - should handle missing childId gracefully', async ({ page }) => {
  50. await page.goto('/pages/games/1a2b');
  51. // Clear any stored user data
  52. await page.evaluate(() => {
  53. uni.removeStorageSync('userId');
  54. uni.removeStorageSync('token');
  55. });
  56. // Start game
  57. await page.fill('input[placeholder="输入4位不重复数字"]', '1234');
  58. await page.click('button:has-text("开始游戏")');
  59. // Game should still work, but score submission should be skipped
  60. // No error should be thrown
  61. await page.waitForSelector('.game-container', { state: 'visible' });
  62. });
  63. test('Sudoku - should display difficulty selection', async ({ page }) => {
  64. await page.goto('/pages/games/sudoku');
  65. // Verify difficulty buttons
  66. await expect(page.locator('.level-btn:has-text("简单")')).toBeVisible();
  67. await expect(page.locator('.level-btn:has-text("中等")')).toBeVisible();
  68. await expect(page.locator('.level-btn:has-text("困难")')).toBeVisible();
  69. });
  70. test('Sudoku - should start game and display board', async ({ page }) => {
  71. await page.goto('/pages/games/sudoku');
  72. // Select difficulty
  73. await page.click('.level-btn:has-text("简单")');
  74. // Start game
  75. await page.click('button:has-text("开始游戏")');
  76. // Verify board is displayed
  77. await expect(page.locator('.board')).toBeVisible();
  78. // Verify number pad
  79. await expect(page.locator('.number-pad')).toBeVisible();
  80. });
  81. test('Sudoku - should calculate score based on difficulty and time', async ({ page }) => {
  82. await page.goto('/pages/games/sudoku');
  83. // Mock API
  84. await page.route('**/api/mini-game/complete', async route => {
  85. await route.fulfill({
  86. status: 200,
  87. body: JSON.stringify({
  88. code: 200,
  89. data: {
  90. pointsEarned: 12,
  91. newBalance: 112
  92. }
  93. })
  94. });
  95. });
  96. // Select hard difficulty
  97. await page.click('.level-btn:has-text("困难")');
  98. await page.click('button:has-text("开始游戏")');
  99. // Simulate completion (in real test, would need to solve the puzzle)
  100. // This is a placeholder for actual game completion logic
  101. const board = page.locator('.board');
  102. await expect(board).toBeVisible();
  103. });
  104. test('API - should call completeMiniGame with correct params', async ({ page }) => {
  105. await page.goto('/pages/games/schulte');
  106. // Intercept API call
  107. let apiCallParams = null;
  108. await page.route('**/api/mini-game/complete', async (route, request) => {
  109. apiCallParams = request.postDataJSON();
  110. await route.fulfill({
  111. status: 200,
  112. body: JSON.stringify({
  113. code: 200,
  114. data: { pointsEarned: 8, newBalance: 108 }
  115. })
  116. });
  117. });
  118. // Start and complete Schulte game
  119. await page.click('button:has-text("开始游戏")');
  120. // Click cells in order (1-25)
  121. // Note: This would need actual cell clicking logic
  122. // For now, we'll just verify the API would be called with correct params
  123. // If game completes, verify API call
  124. if (apiCallParams) {
  125. expect(apiCallParams.childId).toBeDefined();
  126. expect(apiCallParams.gameCode).toBe('schulte');
  127. expect(apiCallParams.completionTime).toBeGreaterThanOrEqual(0);
  128. expect(apiCallParams.score).toBeGreaterThanOrEqual(60);
  129. }
  130. });
  131. test('UI - should display result modal with score', async ({ page }) => {
  132. await page.goto('/pages/games/schulte');
  133. // Mock a completed game state
  134. await page.evaluate(() => {
  135. // This would set the game state to show results
  136. // In a real test, we'd complete the actual game
  137. window.testGameComplete = true;
  138. });
  139. // If result modal is shown, verify it displays score
  140. const resultModal = page.locator('.result-modal');
  141. if (await resultModal.isVisible({ timeout: 5000 }).catch(() => false)) {
  142. await expect(resultModal.locator('.result-title')).toBeVisible();
  143. await expect(resultModal.locator('.result-score')).toContainText('积分');
  144. await expect(resultModal.locator('button:has-text("再来")')).toBeVisible();
  145. }
  146. });
  147. test('Games List - should navigate to all games', async ({ page }) => {
  148. await page.goto('/pages/games/list');
  149. // Verify all game cards are visible
  150. await expect(page.locator('text=舒尔特方格')).toBeVisible();
  151. await expect(page.locator('text=猜数字')).toBeVisible();
  152. await expect(page.locator('text=数独')).toBeVisible();
  153. // Click and verify navigation
  154. await page.click('text=舒尔特方格');
  155. await expect(page).toHaveURL(/.*schulte/);
  156. await page.goBack();
  157. await page.click('text=数独');
  158. await expect(page).toHaveURL(/.*sudoku/);
  159. });
  160. });
  161. // Integration test for score calculation logic
  162. test.describe('Score Calculation Logic', () => {
  163. test('1a2b score calculation', () => {
  164. const calculateScore = (guessesUsed) => Math.max(100 - guessesUsed * 8, 60);
  165. // Test various scenarios
  166. expect(calculateScore(1)).toBe(92);
  167. expect(calculateScore(2)).toBe(84);
  168. expect(calculateScore(5)).toBe(60); // Minimum
  169. expect(calculateScore(10)).toBe(60);
  170. });
  171. test('Sudoku score calculation', () => {
  172. const calculateSudokuScore = (difficulty, time) => {
  173. const baseScore = { easy: 60, medium: 75, hard: 90 };
  174. const base = baseScore[difficulty] || 60;
  175. const timeBonus = Math.max(40 - Math.floor(time / 10), 0);
  176. return base + timeBonus;
  177. };
  178. // Test various scenarios
  179. expect(calculateSudokuScore('easy', 30)).toBe(97);
  180. expect(calculateSudokuScore('medium', 60)).toBe(109);
  181. expect(calculateSudokuScore('hard', 120)).toBe(118);
  182. expect(calculateSudokuScore('easy', 500)).toBe(60); // No time bonus
  183. });
  184. test('Schulte score calculation', () => {
  185. const calculateSchulteScore = (time) => Math.max(100 - Math.floor(time * 2), 60);
  186. expect(calculateSchulteScore(10)).toBe(80);
  187. expect(calculateSchulteScore(20)).toBe(60); // Minimum
  188. expect(calculateSchulteScore(30)).toBe(60);
  189. });
  190. });