Skip to content

Commit 1165740

Browse files
committed
Adds Playwright e2e tests
1 parent f40f8a6 commit 1165740

File tree

9 files changed

+305
-17
lines changed

9 files changed

+305
-17
lines changed

client/.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
# Playwright
3+
node_modules/
4+
/test-results/
5+
/playwright-report/
6+
/blob-report/
7+
/playwright/.cache/

client/e2e-tests/games.spec.ts

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
test.describe('Game Listing and Navigation', () => {
4+
test('should display games with titles on index page', async ({ page }) => {
5+
await page.goto('/');
6+
7+
// Wait for the games to load
8+
await page.waitForSelector('[data-testid="games-grid"]', { timeout: 10000 });
9+
10+
// Check that games are displayed
11+
const gameCards = page.locator('[data-testid="game-card"]');
12+
13+
// Wait for at least one game card to be visible
14+
await expect(gameCards.first()).toBeVisible();
15+
16+
// Check that we have at least one game
17+
const gameCount = await gameCards.count();
18+
expect(gameCount).toBeGreaterThan(0);
19+
20+
// Check that each game card has a title
21+
const firstGameCard = gameCards.first();
22+
await expect(firstGameCard.locator('[data-testid="game-title"]')).toBeVisible();
23+
24+
// Verify that game titles are not empty
25+
const gameTitle = await firstGameCard.locator('[data-testid="game-title"]').textContent();
26+
expect(gameTitle?.trim()).toBeTruthy();
27+
});
28+
29+
test('should navigate to correct game details page when clicking on a game', async ({ page }) => {
30+
await page.goto('/');
31+
32+
// Wait for games to load
33+
await page.waitForSelector('[data-testid="games-grid"]', { timeout: 10000 });
34+
35+
// Get the first game card and its data attributes
36+
const firstGameCard = page.locator('[data-testid="game-card"]').first();
37+
const gameId = await firstGameCard.getAttribute('data-game-id');
38+
const gameTitle = await firstGameCard.getAttribute('data-game-title');
39+
40+
// Click on the first game
41+
await firstGameCard.click();
42+
43+
// Verify we're on the correct game details page
44+
await expect(page).toHaveURL(`/game/${gameId}`);
45+
46+
// Verify the game details page loads
47+
await page.waitForSelector('[data-testid="game-details"]', { timeout: 10000 });
48+
49+
// Verify the title matches what we clicked on
50+
const detailsTitle = page.locator('[data-testid="game-details-title"]');
51+
await expect(detailsTitle).toHaveText(gameTitle || '');
52+
});
53+
54+
test('should display game details with all required information', async ({ page }) => {
55+
// Navigate to a specific game (we'll use game ID 1 as an example)
56+
await page.goto('/game/1');
57+
58+
// Wait for game details to load
59+
await page.waitForSelector('[data-testid="game-details"]', { timeout: 10000 });
60+
61+
// Check that the game title is present and not empty
62+
const gameTitle = page.locator('[data-testid="game-details-title"]');
63+
await expect(gameTitle).toBeVisible();
64+
const titleText = await gameTitle.textContent();
65+
expect(titleText?.trim()).toBeTruthy();
66+
67+
// Check that the game description is present and not empty
68+
const gameDescription = page.locator('[data-testid="game-details-description"]');
69+
await expect(gameDescription).toBeVisible();
70+
const descriptionText = await gameDescription.textContent();
71+
expect(descriptionText?.trim()).toBeTruthy();
72+
73+
// Check that either publisher or category (or both) are present
74+
const publisherExists = await page.locator('[data-testid="game-details-publisher"]').isVisible();
75+
const categoryExists = await page.locator('[data-testid="game-details-category"]').isVisible();
76+
expect(publisherExists && categoryExists).toBeTruthy();
77+
78+
// If publisher exists, check it has content
79+
if (publisherExists) {
80+
const publisherText = await page.locator('[data-testid="game-details-publisher"]').textContent();
81+
expect(publisherText?.trim()).toBeTruthy();
82+
}
83+
84+
// If category exists, check it has content
85+
if (categoryExists) {
86+
const categoryText = await page.locator('[data-testid="game-details-category"]').textContent();
87+
expect(categoryText?.trim()).toBeTruthy();
88+
}
89+
});
90+
91+
test('should display a button to back the game', async ({ page }) => {
92+
await page.goto('/game/1');
93+
94+
// Wait for game details to load
95+
await page.waitForSelector('[data-testid="game-details"]', { timeout: 10000 });
96+
97+
// Check that the back game button is present
98+
const backButton = page.locator('[data-testid="back-game-button"]');
99+
await expect(backButton).toBeVisible();
100+
await expect(backButton).toContainText('Support This Game');
101+
102+
// Verify the button is clickable
103+
await expect(backButton).toBeEnabled();
104+
});
105+
106+
test('should be able to navigate back to home from game details', async ({ page }) => {
107+
await page.goto('/game/1');
108+
109+
// Wait for the page to load
110+
await page.waitForSelector('[data-testid="game-details"]', { timeout: 10000 });
111+
112+
// Find and click the back to all games link
113+
const backLink = page.locator('a:has-text("Back to all games")');
114+
await expect(backLink).toBeVisible();
115+
await backLink.click();
116+
117+
// Verify we're back on the home page
118+
await expect(page).toHaveURL('/');
119+
await page.waitForSelector('[data-testid="games-grid"]', { timeout: 10000 });
120+
});
121+
122+
test('should handle navigation to non-existent game gracefully', async ({ page }) => {
123+
// Navigate to a game that doesn't exist
124+
await page.goto('/game/99999');
125+
126+
// The page should load without crashing
127+
// Check if there's an error message or if it handles gracefully
128+
await page.waitForTimeout(3000);
129+
130+
// The page should either show an error or handle it gracefully
131+
// We expect the page to not crash and still have a valid title
132+
await expect(page).toHaveTitle(/Game Details - Tailspin Toys/);
133+
});
134+
});

client/e2e-tests/home.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
test.describe('Home Page', () => {
4+
test('should display the correct title', async ({ page }) => {
5+
await page.goto('/');
6+
7+
// Check that the page title is correct
8+
await expect(page).toHaveTitle('Tailspin Toys - Crowdfunding your new favorite game!');
9+
});
10+
11+
test('should display the main heading', async ({ page }) => {
12+
await page.goto('/');
13+
14+
// Check that the main heading is present
15+
const mainHeading = page.locator('h1').first();
16+
await expect(mainHeading).toHaveText('Tailspin Toys');
17+
});
18+
19+
test('should display the welcome message', async ({ page }) => {
20+
await page.goto('/');
21+
22+
// Check that the welcome message is present
23+
const welcomeMessage = page.locator('p').first();
24+
await expect(welcomeMessage).toHaveText('Find your next game! And maybe even back one! Explore our collection!');
25+
});
26+
});

client/package-lock.json

Lines changed: 64 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"dev": "astro dev",
77
"build": "astro build",
88
"preview": "astro preview",
9-
"astro": "astro"
9+
"astro": "astro",
10+
"test:e2e": "npx playwright test"
1011
},
1112
"dependencies": {
1213
"@astrojs/node": "^9.2.2",
@@ -17,6 +18,7 @@
1718
"typescript": "^5.8.3"
1819
},
1920
"devDependencies": {
21+
"@playwright/test": "^1.52.0",
2022
"@types/node": "^22.15.21",
2123
"autoprefixer": "^10.4.21",
2224
"postcss": "^8.5.3",

client/playwright.config.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { defineConfig, devices } from '@playwright/test';
2+
3+
/**
4+
* Read environment variables from file.
5+
* https://github.com/motdotla/dotenv
6+
*/
7+
// import dotenv from 'dotenv';
8+
// import path from 'path';
9+
// dotenv.config({ path: path.resolve(__dirname, '.env') });
10+
11+
/**
12+
* See https://playwright.dev/docs/test-configuration.
13+
*/
14+
export default defineConfig({
15+
testDir: './e2e-tests',
16+
/* Run tests in files in parallel */
17+
fullyParallel: true,
18+
/* Fail the build on CI if you accidentally left test.only in the source code. */
19+
forbidOnly: !!process.env.CI,
20+
/* Retry on CI only */
21+
retries: process.env.CI ? 2 : 0,
22+
/* Opt out of parallel tests on CI. */
23+
workers: process.env.CI ? 1 : undefined,
24+
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
25+
reporter: 'list',
26+
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
27+
use: {
28+
/* Base URL to use in actions like `await page.goto('/')`. */
29+
baseURL: 'http://localhost:4321',
30+
31+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
32+
trace: 'on-first-retry',
33+
},
34+
35+
/* Configure projects for major browsers */
36+
projects: [
37+
{
38+
name: 'chromium',
39+
use: { ...devices['Desktop Chrome'] },
40+
},
41+
],
42+
43+
/* Run your local dev server before starting the tests */
44+
webServer: {
45+
command: '../scripts/start-app.sh',
46+
url: 'http://localhost:4321',
47+
reuseExistingServer: !process.env.CI,
48+
timeout: 120 * 1000, // 2 minutes to allow for setup
49+
},
50+
});

client/src/components/GameDetails.svelte

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,14 @@
7676
{error}
7777
</div>
7878
{:else if gameData}
79-
<div class="bg-slate-800/70 backdrop-blur-sm border border-slate-700 rounded-xl overflow-hidden">
79+
<div class="bg-slate-800/70 backdrop-blur-sm border border-slate-700 rounded-xl overflow-hidden" data-testid="game-details">
8080
<div class="p-6">
8181
<div class="flex justify-between items-start flex-wrap gap-3">
82-
<h1 class="text-3xl font-bold text-slate-100 mb-2">{gameData.title}</h1>
82+
<h1 class="text-3xl font-bold text-slate-100 mb-2" data-testid="game-details-title">{gameData.title}</h1>
8383

8484
{#if gameData.starRating !== null}
8585
<div class="flex items-center">
86-
<span class="bg-blue-500/20 text-blue-400 text-sm px-3 py-1 rounded-full">
86+
<span class="bg-blue-500/20 text-blue-400 text-sm px-3 py-1 rounded-full" data-testid="game-rating">
8787
<span class="text-yellow-400">{renderStarRating(gameData.starRating)}</span>
8888
{gameData.starRating.toFixed(1)}
8989
</span>
@@ -93,12 +93,12 @@
9393

9494
<div class="flex flex-wrap gap-2 mt-4 mb-6">
9595
{#if gameData.category}
96-
<span class="text-xs font-medium px-2.5 py-0.5 rounded bg-blue-900/60 text-blue-300">
96+
<span class="text-xs font-medium px-2.5 py-0.5 rounded bg-blue-900/60 text-blue-300" data-testid="game-details-category">
9797
{gameData.category.name}
9898
</span>
9999
{/if}
100100
{#if gameData.publisher}
101-
<span class="text-xs font-medium px-2.5 py-0.5 rounded bg-purple-900/60 text-purple-300">
101+
<span class="text-xs font-medium px-2.5 py-0.5 rounded bg-purple-900/60 text-purple-300" data-testid="game-details-publisher">
102102
{gameData.publisher.name}
103103
</span>
104104
{/if}
@@ -107,12 +107,12 @@
107107
<div class="space-y-4 mt-6">
108108
<h2 class="text-lg font-semibold text-slate-200 mb-2">About this game</h2>
109109
<div class="text-slate-400 space-y-4">
110-
<p>{gameData.description}</p>
110+
<p data-testid="game-details-description">{gameData.description}</p>
111111
</div>
112112
</div>
113113

114114
<div class="mt-8">
115-
<button class="w-full bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-500 hover:to-purple-500 text-white font-medium py-3 px-4 rounded-lg transition-all duration-200 flex justify-center items-center">
115+
<button class="w-full bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-500 hover:to-purple-500 text-white font-medium py-3 px-4 rounded-lg transition-all duration-200 flex justify-center items-center" data-testid="back-game-button">
116116
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor">
117117
<path fill-rule="evenodd" d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z" clip-rule="evenodd" />
118118
</svg>

0 commit comments

Comments
 (0)