mirror of
https://github.com/ghndrx/k8s-game-2048.git
synced 2026-02-10 06:45:07 +00:00
feat: add comprehensive Playwright testing suite with visual regression
- Add Playwright configuration with multi-browser testing - Create basic functionality tests for game mechanics - Add gameplay tests with keyboard and touch interactions - Implement visual regression testing with screenshots - Add environment-specific tests for dev/staging/prod - Include health endpoint and security header validation - Set up test infrastructure for CI/CD pipeline
This commit is contained in:
58
tests/basic.spec.ts
Normal file
58
tests/basic.spec.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('2048 Game - Basic Functionality', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
test('should load the game successfully', async ({ page }) => {
|
||||
// Check title
|
||||
await expect(page).toHaveTitle(/2048/);
|
||||
|
||||
// Check main elements are present
|
||||
await expect(page.locator('h1')).toContainText('2048');
|
||||
await expect(page.locator('.game-container')).toBeVisible();
|
||||
await expect(page.locator('.grid-container')).toBeVisible();
|
||||
|
||||
// Check score displays
|
||||
await expect(page.locator('#score')).toBeVisible();
|
||||
await expect(page.locator('#best')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show environment badge', async ({ page }) => {
|
||||
const envBadge = page.locator('#env-badge');
|
||||
await expect(envBadge).toBeVisible();
|
||||
|
||||
// Should have one of the environment classes
|
||||
const badgeClass = await envBadge.getAttribute('class');
|
||||
expect(badgeClass).toMatch(/(development|staging|production)/);
|
||||
});
|
||||
|
||||
test('should have initial tiles on game start', async ({ page }) => {
|
||||
// Should have at least 2 tiles initially
|
||||
const tiles = page.locator('.tile');
|
||||
await expect(tiles).toHaveCount(2);
|
||||
|
||||
// Tiles should have values 2 or 4
|
||||
const tileTexts = await tiles.allTextContents();
|
||||
tileTexts.forEach(text => {
|
||||
expect(['2', '4']).toContain(text);
|
||||
});
|
||||
});
|
||||
|
||||
test('should restart game when restart button is clicked', async ({ page }) => {
|
||||
const restartButton = page.locator('#restart-button');
|
||||
await expect(restartButton).toBeVisible();
|
||||
|
||||
// Click restart
|
||||
await restartButton.click();
|
||||
|
||||
// Should have exactly 2 tiles after restart
|
||||
const tiles = page.locator('.tile');
|
||||
await expect(tiles).toHaveCount(2);
|
||||
|
||||
// Score should be reset to 0
|
||||
await expect(page.locator('#score')).toHaveText('0');
|
||||
});
|
||||
});
|
||||
75
tests/environment.spec.ts
Normal file
75
tests/environment.spec.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('2048 Game - Environment Tests', () => {
|
||||
test('should display correct environment in development', async ({ page }) => {
|
||||
// This test will run when BASE_URL contains 'dev'
|
||||
const baseUrl = process.env.BASE_URL || '';
|
||||
test.skip(!baseUrl.includes('dev'), 'Development environment test');
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
const envElement = page.locator('#environment');
|
||||
await expect(envElement).toContainText('Development');
|
||||
|
||||
const envBadge = page.locator('#env-badge');
|
||||
await expect(envBadge).toHaveClass(/development/);
|
||||
});
|
||||
|
||||
test('should display correct environment in staging', async ({ page }) => {
|
||||
const baseUrl = process.env.BASE_URL || '';
|
||||
test.skip(!baseUrl.includes('staging'), 'Staging environment test');
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
const envElement = page.locator('#environment');
|
||||
await expect(envElement).toContainText('Staging');
|
||||
|
||||
const envBadge = page.locator('#env-badge');
|
||||
await expect(envBadge).toHaveClass(/staging/);
|
||||
});
|
||||
|
||||
test('should display correct environment in production', async ({ page }) => {
|
||||
const baseUrl = process.env.BASE_URL || '';
|
||||
test.skip(baseUrl.includes('dev') || baseUrl.includes('staging'), 'Production environment test');
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
const envElement = page.locator('#environment');
|
||||
await expect(envElement).toContainText('Production');
|
||||
|
||||
const envBadge = page.locator('#env-badge');
|
||||
await expect(envBadge).toHaveClass(/production/);
|
||||
});
|
||||
|
||||
test('should have working health endpoint', async ({ request }) => {
|
||||
const response = await request.get('/health');
|
||||
expect(response.status()).toBe(200);
|
||||
|
||||
const text = await response.text();
|
||||
expect(text).toContain('healthy');
|
||||
});
|
||||
|
||||
test('should load all assets successfully', async ({ page }) => {
|
||||
const responses: any[] = [];
|
||||
|
||||
page.on('response', response => {
|
||||
responses.push(response);
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Check that all resources loaded successfully
|
||||
const failedResponses = responses.filter(response => response.status() >= 400);
|
||||
expect(failedResponses.length).toBe(0);
|
||||
});
|
||||
|
||||
test('should have correct security headers', async ({ request }) => {
|
||||
const response = await request.get('/');
|
||||
|
||||
// Check for basic security headers
|
||||
expect(response.headers()['x-frame-options']).toBeDefined();
|
||||
expect(response.headers()['x-content-type-options']).toBeDefined();
|
||||
expect(response.headers()['x-xss-protection']).toBeDefined();
|
||||
});
|
||||
});
|
||||
83
tests/gameplay.spec.ts
Normal file
83
tests/gameplay.spec.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('2048 Game - Gameplay Tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
test('should move tiles with arrow keys', async ({ page }) => {
|
||||
// Get initial tile positions
|
||||
const initialTiles = await page.locator('.tile').all();
|
||||
const initialPositions = [];
|
||||
|
||||
for (const tile of initialTiles) {
|
||||
const style = await tile.getAttribute('style');
|
||||
initialPositions.push(style);
|
||||
}
|
||||
|
||||
// Press arrow key to move tiles
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await page.waitForTimeout(500); // Wait for animation
|
||||
|
||||
// Check that tiles moved or new tile appeared
|
||||
const newTiles = await page.locator('.tile').all();
|
||||
expect(newTiles.length).toBeGreaterThanOrEqual(2);
|
||||
|
||||
// At least one tile should have moved or new tile should appear
|
||||
let tilesChanged = newTiles.length > initialTiles.length;
|
||||
|
||||
if (!tilesChanged) {
|
||||
for (let i = 0; i < Math.min(newTiles.length, initialPositions.length); i++) {
|
||||
const newStyle = await newTiles[i].getAttribute('style');
|
||||
if (newStyle !== initialPositions[i]) {
|
||||
tilesChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(tilesChanged).toBe(true);
|
||||
});
|
||||
|
||||
test('should handle touch/swipe on mobile', async ({ page, isMobile }) => {
|
||||
test.skip(!isMobile, 'Touch test only for mobile');
|
||||
|
||||
const gameContainer = page.locator('.game-container');
|
||||
|
||||
// Simulate swipe right
|
||||
await gameContainer.touchStart([{ x: 100, y: 200 }]);
|
||||
await gameContainer.touchEnd([{ x: 300, y: 200 }]);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Should have tiles after swipe
|
||||
const tiles = page.locator('.tile');
|
||||
await expect(tiles).toHaveCount.atLeast(2);
|
||||
});
|
||||
|
||||
test('should update score when tiles merge', async ({ page }) => {
|
||||
// This test might need multiple moves to get mergeable tiles
|
||||
// For now, just verify score element updates
|
||||
const scoreElement = page.locator('#score');
|
||||
const initialScore = await scoreElement.textContent();
|
||||
|
||||
// Try multiple moves to potentially trigger a merge
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await page.waitForTimeout(200);
|
||||
await page.keyboard.press('ArrowDown');
|
||||
await page.waitForTimeout(200);
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
await page.waitForTimeout(200);
|
||||
await page.keyboard.press('ArrowUp');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
const currentScore = await scoreElement.textContent();
|
||||
if (currentScore !== initialScore) {
|
||||
expect(parseInt(currentScore || '0')).toBeGreaterThan(parseInt(initialScore || '0'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
14
tests/package.json
Normal file
14
tests/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "playwright-tests",
|
||||
"version": "1.0.0",
|
||||
"description": "Playwright tests for 2048 game",
|
||||
"scripts": {
|
||||
"test": "playwright test",
|
||||
"test:headed": "playwright test --headed",
|
||||
"test:debug": "playwright test --debug",
|
||||
"test:ui": "playwright test --ui"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.40.0"
|
||||
}
|
||||
}
|
||||
60
tests/visual.spec.ts
Normal file
60
tests/visual.spec.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('2048 Game - Visual Tests & Screenshots', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
test('should match initial game state screenshot', async ({ page }) => {
|
||||
// Wait for game to fully load
|
||||
await page.waitForSelector('.tile', { timeout: 5000 });
|
||||
|
||||
// Take screenshot of initial game state
|
||||
await expect(page).toHaveScreenshot('initial-game-state.png', {
|
||||
fullPage: true,
|
||||
animations: 'disabled'
|
||||
});
|
||||
});
|
||||
|
||||
test('should match game container layout', async ({ page }) => {
|
||||
const gameContainer = page.locator('.game-container');
|
||||
await expect(gameContainer).toHaveScreenshot('game-container.png', {
|
||||
animations: 'disabled'
|
||||
});
|
||||
});
|
||||
|
||||
test('should match header with scores', async ({ page }) => {
|
||||
const header = page.locator('.header');
|
||||
await expect(header).toHaveScreenshot('header-scores.png', {
|
||||
animations: 'disabled'
|
||||
});
|
||||
});
|
||||
|
||||
test('should match environment badge', async ({ page }) => {
|
||||
const envBadge = page.locator('#env-badge');
|
||||
await expect(envBadge).toHaveScreenshot('environment-badge.png', {
|
||||
animations: 'disabled'
|
||||
});
|
||||
});
|
||||
|
||||
test('should display correctly on mobile', async ({ page, isMobile }) => {
|
||||
test.skip(!isMobile, 'Mobile test only');
|
||||
|
||||
await expect(page).toHaveScreenshot('mobile-game.png', {
|
||||
fullPage: true,
|
||||
animations: 'disabled'
|
||||
});
|
||||
});
|
||||
|
||||
test('should show game over state', async ({ page }) => {
|
||||
// Try to fill the board quickly (this is a simplified approach)
|
||||
// In a real scenario, we might need to manipulate the game state directly
|
||||
|
||||
// Take screenshot of current state for comparison
|
||||
await expect(page).toHaveScreenshot('game-in-progress.png', {
|
||||
fullPage: true,
|
||||
animations: 'disabled'
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user