Skip to content

Commit 4f6f016

Browse files
CopilotProLoser
andcommitted
feat: add Playwright E2E tests for critical game functionality
Co-authored-by: ProLoser <67395+ProLoser@users.noreply.github.com>
1 parent 44b28b1 commit 4f6f016

File tree

5 files changed

+173
-1
lines changed

5 files changed

+173
-1
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,7 @@ dev-dist
9797
*.sln
9898
*.sw?
9999

100-
### /Vite ###
100+
### /Vite ###
101+
# Playwright
102+
test-results/
103+
playwright-report/

e2e/game.spec.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { test, expect, type Page } from '@playwright/test';
2+
3+
async function waitForBoard(page: Page) {
4+
await page.goto('/');
5+
await page.locator('#board').waitFor({ state: 'visible' });
6+
}
7+
8+
test.describe('Game board', () => {
9+
test.beforeEach(async ({ page }) => {
10+
await waitForBoard(page);
11+
});
12+
13+
test('renders the board with toolbar and dice', async ({ page }) => {
14+
await expect(page.locator('#board')).toBeVisible();
15+
await expect(page.locator('#toolbar')).toBeVisible();
16+
await expect(page.locator('.dice')).toBeVisible();
17+
});
18+
19+
test('displays 24 points, 2 bar areas, and 2 home areas', async ({ page }) => {
20+
await expect(page.locator('.point')).toHaveCount(24);
21+
await expect(page.locator('.bar')).toHaveCount(2);
22+
await expect(page.locator('.home')).toHaveCount(2);
23+
});
24+
25+
test('toolbar shows offline game label when no opponent is loaded', async ({ page }) => {
26+
await expect(page.locator('#toolbar h2')).toHaveText('Offline Game');
27+
});
28+
29+
test('pieces are present on the starting board', async ({ page }) => {
30+
const pieces = page.locator('.piece:not(.ghost)');
31+
await expect(pieces).toHaveCount(30);
32+
});
33+
});
34+
35+
test.describe('Dice', () => {
36+
test.beforeEach(async ({ page }) => {
37+
await waitForBoard(page);
38+
});
39+
40+
test('dice start in pulsating rolling state', async ({ page }) => {
41+
await expect(page.locator('.dice.pulsate')).toBeVisible();
42+
});
43+
44+
test('rolling dice transitions from rolling to moving state', async ({ page }) => {
45+
await expect(page.locator('.dice.pulsate')).toBeVisible();
46+
await page.locator('.dice').click();
47+
await expect(page.locator('.dice')).not.toHaveClass(/pulsate/);
48+
});
49+
50+
test('rolling dice shows two die images', async ({ page }) => {
51+
await page.locator('.dice').click();
52+
await expect(page.locator('.dice img')).toHaveCount(2);
53+
});
54+
});
55+
56+
test.describe('Piece selection', () => {
57+
test.beforeEach(async ({ page }) => {
58+
await waitForBoard(page);
59+
});
60+
61+
test('clicking a point selects it in local mode', async ({ page }) => {
62+
const point = page.locator('.point').first();
63+
await point.click();
64+
await expect(point).toHaveClass(/selected/);
65+
});
66+
67+
test('clicking a selected point deselects it', async ({ page }) => {
68+
const point = page.locator('.point').first();
69+
await point.click();
70+
await expect(point).toHaveClass(/selected/);
71+
await point.click();
72+
await expect(point).not.toHaveClass(/selected/);
73+
});
74+
75+
test('selecting a different point moves the selection', async ({ page }) => {
76+
const firstPoint = page.locator('.point').nth(0);
77+
const secondPoint = page.locator('.point').nth(11);
78+
await firstPoint.click();
79+
await expect(firstPoint).toHaveClass(/selected/);
80+
await secondPoint.click();
81+
await expect(firstPoint).not.toHaveClass(/selected/);
82+
await expect(secondPoint).toHaveClass(/selected/);
83+
});
84+
});
85+
86+
test.describe('Login dialog', () => {
87+
test.beforeEach(async ({ page }) => {
88+
await waitForBoard(page);
89+
});
90+
91+
test('dialog is initially closed', async ({ page }) => {
92+
await expect(page.locator('dialog')).not.toBeVisible();
93+
});
94+
95+
test('toolbar click opens the login dialog', async ({ page }) => {
96+
await page.locator('#toolbar').click();
97+
await expect(page.locator('dialog[open]')).toBeVisible();
98+
await expect(page.locator('#login')).toBeVisible();
99+
});
100+
101+
test('login dialog contains a menu button and title', async ({ page }) => {
102+
await page.locator('#toolbar').click();
103+
await expect(page.locator('#login button[aria-haspopup="menu"]')).toBeVisible();
104+
await expect(page.locator('#login h1')).toBeVisible();
105+
});
106+
107+
test('clicking outside the dialog closes it', async ({ page }) => {
108+
await page.locator('#toolbar').click();
109+
await expect(page.locator('dialog[open]')).toBeVisible();
110+
await page.locator('#board').click({ position: { x: 5, y: 5 }, force: true });
111+
await expect(page.locator('dialog')).not.toBeVisible();
112+
});
113+
});

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"test": "jest",
1717
"test:watch": "jest --watch",
1818
"test:coverage": "jest --coverage",
19+
"test:e2e": "playwright test",
1920
"preview": "vite preview",
2021
"functions": "yarn workspace functions deploy"
2122
},
@@ -41,6 +42,7 @@
4142
},
4243
"devDependencies": {
4344
"@jest/globals": "^30.2.0",
45+
"@playwright/test": "1.58.2",
4446
"@testing-library/dom": "^10.4.1",
4547
"@testing-library/jest-dom": "^6.9.1",
4648
"@testing-library/react": "^16.3.0",

playwright.config.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { defineConfig, devices } from '@playwright/test';
2+
3+
export default defineConfig({
4+
testDir: './e2e',
5+
timeout: 30000,
6+
fullyParallel: true,
7+
forbidOnly: !!process.env.CI,
8+
retries: process.env.CI ? 2 : 0,
9+
reporter: process.env.CI ? 'github' : 'list',
10+
use: {
11+
baseURL: 'http://localhost:5173',
12+
locale: 'en-US',
13+
trace: 'on-first-retry',
14+
screenshot: 'only-on-failure',
15+
},
16+
projects: [
17+
{
18+
name: 'chromium',
19+
use: { ...devices['Desktop Chrome'] },
20+
},
21+
],
22+
webServer: {
23+
command: 'yarn start',
24+
port: 5173,
25+
reuseExistingServer: !process.env.CI,
26+
timeout: 60000, // 60 seconds for initial build
27+
},
28+
});

yarn.lock

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3264,6 +3264,13 @@
32643264
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.9.tgz#d229a7b7f9dac167a156992ef23c7f023653f53b"
32653265
integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==
32663266

3267+
"@playwright/test@1.58.2":
3268+
version "1.58.2"
3269+
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.58.2.tgz#b0ad585d2e950d690ef52424967a42f40c6d2cbd"
3270+
integrity sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==
3271+
dependencies:
3272+
playwright "1.58.2"
3273+
32673274
"@pnpm/config.env-replace@^1.1.0":
32683275
version "1.1.0"
32693276
resolved "https://registry.yarnpkg.com/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz#ab29da53df41e8948a00f2433f085f54de8b3a4c"
@@ -6779,6 +6786,11 @@ fs.realpath@^1.0.0:
67796786
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
67806787
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
67816788

6789+
fsevents@2.3.2:
6790+
version "2.3.2"
6791+
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
6792+
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
6793+
67826794
fsevents@^2.3.3, fsevents@~2.3.2, fsevents@~2.3.3:
67836795
version "2.3.3"
67846796
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
@@ -9767,6 +9779,20 @@ pkg-dir@^4.2.0:
97679779
dependencies:
97689780
find-up "^4.0.0"
97699781

9782+
playwright-core@1.58.2:
9783+
version "1.58.2"
9784+
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.58.2.tgz#ac5f5b4b10d29bcf934415f0b8d133b34b0dcb13"
9785+
integrity sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==
9786+
9787+
playwright@1.58.2:
9788+
version "1.58.2"
9789+
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.58.2.tgz#afe547164539b0bcfcb79957394a7a3fa8683cfd"
9790+
integrity sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==
9791+
dependencies:
9792+
playwright-core "1.58.2"
9793+
optionalDependencies:
9794+
fsevents "2.3.2"
9795+
97709796
portfinder@^1.0.32:
97719797
version "1.0.38"
97729798
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.38.tgz#e4fb3a2d888b20d2977da050e48ab5e1f57a185e"

0 commit comments

Comments
 (0)