Skip to content

Commit 6c8a29c

Browse files
authored
Merge pull request #234 from Hallab7/test/add-e2e-property-purchase
feat: Add E2E test coverage for property purchase flow with MSW
2 parents 2b94caf + 8026573 commit 6c8a29c

12 files changed

Lines changed: 1025 additions & 29 deletions

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"test:coverage": "jest --coverage",
2121
"test:ci": "jest --coverage --watchAll=false --ci",
2222
"test:e2e": "playwright test",
23+
"test:e2e:mock": "SKIP_WEBSERVER=true playwright test tests/e2e/property-purchase-flow.spec.ts",
2324
"test:e2e:ui": "playwright test --ui",
2425
"test:e2e:debug": "playwright test --debug",
2526
"test:e2e:install": "playwright install",
@@ -30,8 +31,6 @@
3031
},
3132
"dependencies": {
3233
"@coinbase/wallet-sdk": "^4.3.7",
33-
"ioredis": "^5.3.2",
34-
"redis": "^4.6.10",
3534
"@hookform/resolvers": "^5.2.2",
3635
"@metamask/sdk": "^0.33.1",
3736
"@radix-ui/react-accordion": "^1.2.12",
@@ -77,6 +76,7 @@
7776
"i18next": "^25.8.13",
7877
"i18next-browser-languagedetector": "^8.2.1",
7978
"input-otp": "^1.4.2",
79+
"ioredis": "^5.3.2",
8080
"jspdf": "^4.0.0",
8181
"jspdf-autotable": "^5.0.7",
8282
"leaflet": "^1.9.4",
@@ -92,6 +92,7 @@
9292
"react-leaflet-cluster": "^4.1.3",
9393
"react-resizable-panels": "^4.4.1",
9494
"recharts": "^2.15.4",
95+
"redis": "^4.6.10",
9596
"sonner": "^2.0.7",
9697
"tailwind-merge": "^3.4.0",
9798
"vaul": "^1.1.2",
@@ -135,6 +136,7 @@
135136
"jest": "^29.7.0",
136137
"jest-axe": "^10.0.0",
137138
"jest-environment-jsdom": "^29.7.0",
139+
"msw": "^2.13.6",
138140
"playwright": "^1.58.2",
139141
"postcss": "^8.5.3",
140142
"storybook": "^10.3.3",

playwright.config.ts

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -40,30 +40,10 @@ export default defineConfig({
4040
name: 'chromium',
4141
use: { ...devices['Desktop Chrome'] },
4242
},
43-
44-
{
45-
name: 'firefox',
46-
use: { ...devices['Desktop Firefox'] },
47-
},
48-
49-
{
50-
name: 'webkit',
51-
use: { ...devices['Desktop Safari'] },
52-
},
53-
54-
/* Test against mobile viewports. */
55-
{
56-
name: 'Mobile Chrome',
57-
use: { ...devices['Pixel 5'] },
58-
},
59-
{
60-
name: 'Mobile Safari',
61-
use: { ...devices['iPhone 12'] },
62-
},
6343
],
6444

6545
/* Run your local dev server before starting the tests */
66-
webServer: {
46+
webServer: process.env.SKIP_WEBSERVER ? undefined : {
6747
command: 'npm run dev',
6848
url: 'http://localhost:3000',
6949
reuseExistingServer: !process.env.CI,

tests/e2e/global-setup.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { chromium, FullConfig } from '@playwright/test';
2+
3+
async function globalSetup(config: FullConfig) {
4+
// Start a browser to generate MSW service worker
5+
const browser = await chromium.launch();
6+
const page = await browser.newPage();
7+
8+
// Navigate to the app to ensure service worker is registered
9+
const baseURL = config.projects[0].use.baseURL || 'http://localhost:3000';
10+
11+
try {
12+
await page.goto(baseURL, { waitUntil: 'networkidle', timeout: 30000 });
13+
14+
// Wait for service worker to be ready
15+
await page.waitForTimeout(2000);
16+
} catch (error) {
17+
console.warn('Could not initialize MSW service worker:', error);
18+
} finally {
19+
await browser.close();
20+
}
21+
}
22+
23+
export default globalSetup;

tests/e2e/msw-verification.spec.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
/**
4+
* MSW Verification Test
5+
* Verifies that MSW is properly mocking API calls without requiring a backend
6+
*/
7+
8+
test.describe('MSW API Mocking Verification', () => {
9+
test('should mock API responses successfully', async ({ page }) => {
10+
// Mock API endpoint
11+
await page.route('**/api/test', async (route) => {
12+
await route.fulfill({
13+
status: 200,
14+
contentType: 'application/json',
15+
body: JSON.stringify({
16+
success: true,
17+
message: 'MSW is working',
18+
data: { test: 'value' },
19+
}),
20+
});
21+
});
22+
23+
// Create a test page that makes an API call
24+
await page.setContent(`
25+
<!DOCTYPE html>
26+
<html>
27+
<head><title>MSW Test</title></head>
28+
<body>
29+
<div id="result">Loading...</div>
30+
<script>
31+
fetch('/api/test')
32+
.then(res => res.json())
33+
.then(data => {
34+
document.getElementById('result').textContent = data.message;
35+
})
36+
.catch(err => {
37+
document.getElementById('result').textContent = 'Error: ' + err.message;
38+
});
39+
</script>
40+
</body>
41+
</html>
42+
`);
43+
44+
// Wait for the API call to complete
45+
await page.waitForTimeout(1000);
46+
47+
// Verify the mocked response was used
48+
const result = await page.locator('#result').textContent();
49+
expect(result).toBe('MSW is working');
50+
});
51+
52+
test('should mock property API endpoints', async ({ page }) => {
53+
// Mock property listings endpoint
54+
await page.route('**/api/properties', async (route) => {
55+
await route.fulfill({
56+
status: 200,
57+
contentType: 'application/json',
58+
body: JSON.stringify({
59+
properties: [
60+
{
61+
id: 'test-1',
62+
name: 'Test Property',
63+
price: { total: 1000000, perToken: 100, currency: 'USD' },
64+
},
65+
],
66+
total: 1,
67+
page: 1,
68+
totalPages: 1,
69+
}),
70+
});
71+
});
72+
73+
// Create a test page
74+
await page.setContent(`
75+
<!DOCTYPE html>
76+
<html>
77+
<head><title>Property Test</title></head>
78+
<body>
79+
<div id="property-count">0</div>
80+
<div id="property-name"></div>
81+
<script>
82+
fetch('/api/properties')
83+
.then(res => res.json())
84+
.then(data => {
85+
document.getElementById('property-count').textContent = data.total;
86+
document.getElementById('property-name').textContent = data.properties[0].name;
87+
});
88+
</script>
89+
</body>
90+
</html>
91+
`);
92+
93+
await page.waitForTimeout(1000);
94+
95+
expect(await page.locator('#property-count').textContent()).toBe('1');
96+
expect(await page.locator('#property-name').textContent()).toBe('Test Property');
97+
});
98+
99+
test('should mock purchase transaction endpoint', async ({ page }) => {
100+
await page.route('**/api/properties/*/purchase', async (route) => {
101+
const postData = route.request().postDataJSON();
102+
103+
await route.fulfill({
104+
status: 200,
105+
contentType: 'application/json',
106+
body: JSON.stringify({
107+
success: true,
108+
transactionHash: '0xmocked123',
109+
amount: postData.amount,
110+
totalCost: postData.amount * 100,
111+
}),
112+
});
113+
});
114+
115+
await page.setContent(`
116+
<!DOCTYPE html>
117+
<html>
118+
<head><title>Purchase Test</title></head>
119+
<body>
120+
<div id="tx-hash"></div>
121+
<div id="cost"></div>
122+
<script>
123+
fetch('/api/properties/test-1/purchase', {
124+
method: 'POST',
125+
headers: { 'Content-Type': 'application/json' },
126+
body: JSON.stringify({ amount: 10, walletAddress: '0x123' })
127+
})
128+
.then(res => res.json())
129+
.then(data => {
130+
document.getElementById('tx-hash').textContent = data.transactionHash;
131+
document.getElementById('cost').textContent = data.totalCost;
132+
});
133+
</script>
134+
</body>
135+
</html>
136+
`);
137+
138+
await page.waitForTimeout(1000);
139+
140+
expect(await page.locator('#tx-hash').textContent()).toBe('0xmocked123');
141+
expect(await page.locator('#cost').textContent()).toBe('1000');
142+
});
143+
});

0 commit comments

Comments
 (0)