Skip to content

Commit 228797f

Browse files
Add Playwright E2E testing setup with complete checkout flow
1 parent 2dc3e5b commit 228797f

10 files changed

Lines changed: 376 additions & 0 deletions

File tree

apps/e2e/README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# E2E Testing with Playwright
2+
3+
This package contains end-to-end tests for the application using Playwright.
4+
5+
## Directory Structure
6+
7+
```
8+
apps/e2e/
9+
├── fixtures/ # Test fixtures and setup
10+
├── page-objects/ # Page Object Models
11+
├── tests/ # Test files
12+
├── playwright.config.ts # Playwright configuration
13+
└── package.json # Package configuration
14+
```
15+
16+
## Running Tests
17+
18+
From the root of the monorepo:
19+
20+
```bash
21+
# Run all tests
22+
yarn workspace e2e test
23+
24+
# Run tests with UI
25+
yarn workspace e2e test:ui
26+
27+
# Run tests in debug mode
28+
yarn workspace e2e test:debug
29+
30+
# View test report
31+
yarn workspace e2e report
32+
```
33+
34+
## Test Flow
35+
36+
The tests cover the following flow:
37+
38+
1. Visit the home page or product detail page
39+
2. Add a product to the cart
40+
3. Open the cart drawer
41+
4. Verify the cart contains the added item
42+
5. Proceed to checkout
43+
6. Fill in the address form
44+
7. Select the test payment option
45+
8. Complete the checkout
46+
9. Verify successful checkout
47+
48+
## Page Objects
49+
50+
- `HomePage`: Handles interactions on the home page
51+
- `ProductPage`: Handles interactions on the product detail page
52+
- `CartDrawer`: Handles interactions with the cart drawer
53+
- `CheckoutPage`: Handles interactions on the checkout page
54+
55+
## Customizing Tests
56+
57+
To add new tests or modify existing ones, update the files in the `tests` directory. To add new page objects, add new files to the `page-objects` directory.
58+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { test as base } from '@playwright/test';
2+
3+
// Define a fixture that sets up the test environment
4+
export const test = base.extend({
5+
// Add any global setup here if needed
6+
});
7+
8+
export { expect } from '@playwright/test';
9+

apps/e2e/package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "e2e",
3+
"version": "1.0.0",
4+
"private": true,
5+
"scripts": {
6+
"test": "playwright test",
7+
"test:ui": "playwright test --ui",
8+
"test:debug": "playwright test --debug",
9+
"report": "playwright show-report"
10+
},
11+
"devDependencies": {
12+
"@playwright/test": "^1.44.0"
13+
}
14+
}
15+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Page, Locator } from '@playwright/test';
2+
3+
export class CartDrawer {
4+
readonly page: Page;
5+
readonly cartDrawer: Locator;
6+
readonly checkoutButton: Locator;
7+
readonly cartItems: Locator;
8+
9+
constructor(page: Page) {
10+
this.page = page;
11+
this.cartDrawer = page.locator('[data-testid="cart-drawer"]');
12+
this.checkoutButton = page.locator('[data-testid="checkout-button"]');
13+
this.cartItems = page.locator('[data-testid="cart-item"]');
14+
}
15+
16+
async waitForDrawerOpen() {
17+
await this.cartDrawer.waitFor({ state: 'visible' });
18+
}
19+
20+
async proceedToCheckout() {
21+
await this.checkoutButton.click();
22+
}
23+
24+
async verifyItemInCart() {
25+
await this.cartItems.first().waitFor({ state: 'visible' });
26+
}
27+
}
28+
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Page, Locator } from '@playwright/test';
2+
3+
export class CheckoutPage {
4+
readonly page: Page;
5+
readonly emailInput: Locator;
6+
readonly firstNameInput: Locator;
7+
readonly lastNameInput: Locator;
8+
readonly addressInput: Locator;
9+
readonly cityInput: Locator;
10+
readonly stateInput: Locator;
11+
readonly zipInput: Locator;
12+
readonly phoneInput: Locator;
13+
readonly testPaymentOption: Locator;
14+
readonly checkoutButton: Locator;
15+
16+
constructor(page: Page) {
17+
this.page = page;
18+
this.emailInput = page.locator('[data-testid="email"]');
19+
this.firstNameInput = page.locator('[data-testid="firstName"]');
20+
this.lastNameInput = page.locator('[data-testid="lastName"]');
21+
this.addressInput = page.locator('[data-testid="address"]');
22+
this.cityInput = page.locator('[data-testid="city"]');
23+
this.stateInput = page.locator('[data-testid="state"]');
24+
this.zipInput = page.locator('[data-testid="zip"]');
25+
this.phoneInput = page.locator('[data-testid="phone"]');
26+
this.testPaymentOption = page.locator('[data-testid="test-payment"]');
27+
this.checkoutButton = page.locator('[data-testid="checkout-test-payment-button"]');
28+
}
29+
30+
async fillAddressForm(customerInfo: {
31+
email: string;
32+
firstName: string;
33+
lastName: string;
34+
address: string;
35+
city: string;
36+
state: string;
37+
zip: string;
38+
phone: string;
39+
}) {
40+
await this.emailInput.fill(customerInfo.email);
41+
await this.firstNameInput.fill(customerInfo.firstName);
42+
await this.lastNameInput.fill(customerInfo.lastName);
43+
await this.addressInput.fill(customerInfo.address);
44+
await this.cityInput.fill(customerInfo.city);
45+
await this.stateInput.fill(customerInfo.state);
46+
await this.zipInput.fill(customerInfo.zip);
47+
await this.phoneInput.fill(customerInfo.phone);
48+
}
49+
50+
async selectTestPayment() {
51+
await this.testPaymentOption.click();
52+
}
53+
54+
async completeCheckout() {
55+
await this.checkoutButton.click();
56+
}
57+
}
58+

apps/e2e/page-objects/home-page.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Page, Locator } from '@playwright/test';
2+
3+
export class HomePage {
4+
readonly page: Page;
5+
readonly addToCartButton: Locator;
6+
readonly cartDrawerButton: Locator;
7+
8+
constructor(page: Page) {
9+
this.page = page;
10+
this.addToCartButton = page.locator('[data-testid="add-to-cart"]');
11+
this.cartDrawerButton = page.locator('[data-testid="cart-button"]');
12+
}
13+
14+
async goto() {
15+
await this.page.goto('/');
16+
}
17+
18+
async addToCart() {
19+
await this.addToCartButton.click();
20+
}
21+
22+
async openCartDrawer() {
23+
await this.cartDrawerButton.click();
24+
}
25+
}
26+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Page, Locator } from '@playwright/test';
2+
3+
export class ProductPage {
4+
readonly page: Page;
5+
readonly addToCartButton: Locator;
6+
readonly cartDrawerButton: Locator;
7+
8+
constructor(page: Page) {
9+
this.page = page;
10+
this.addToCartButton = page.locator('[data-testid="add-to-cart"]');
11+
this.cartDrawerButton = page.locator('[data-testid="cart-button"]');
12+
}
13+
14+
async goto(productHandle: string) {
15+
await this.page.goto(`/products/${productHandle}`);
16+
}
17+
18+
async addToCart() {
19+
await this.addToCartButton.click();
20+
}
21+
22+
async openCartDrawer() {
23+
await this.cartDrawerButton.click();
24+
}
25+
}
26+

apps/e2e/playwright.config.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { defineConfig, devices } from '@playwright/test';
2+
3+
export default defineConfig({
4+
testDir: './tests',
5+
timeout: 60000,
6+
fullyParallel: true,
7+
forbidOnly: !!process.env.CI,
8+
retries: process.env.CI ? 2 : 0,
9+
workers: process.env.CI ? 1 : undefined,
10+
reporter: [
11+
['html'],
12+
['list']
13+
],
14+
use: {
15+
baseURL: process.env.BASE_URL || 'http://localhost:3000',
16+
trace: 'on-first-retry',
17+
screenshot: 'only-on-failure',
18+
video: 'on-first-retry',
19+
headless: !!process.env.CI,
20+
viewport: { width: 1280, height: 720 },
21+
actionTimeout: 15000,
22+
navigationTimeout: 30000,
23+
},
24+
projects: [
25+
{
26+
name: 'chromium',
27+
use: { ...devices['Desktop Chrome'] },
28+
},
29+
],
30+
webServer: {
31+
command: 'cd ../.. && yarn dev',
32+
url: 'http://localhost:3000',
33+
reuseExistingServer: !process.env.CI,
34+
timeout: 120000,
35+
},
36+
});
37+
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { test, expect } from '../fixtures/test-setup.fixture';
2+
import { HomePage } from '../page-objects/home-page';
3+
import { ProductPage } from '../page-objects/product-page';
4+
import { CartDrawer } from '../page-objects/cart-drawer';
5+
import { CheckoutPage } from '../page-objects/checkout-page';
6+
7+
test.describe('Complete Checkout Flow', () => {
8+
test('should complete a purchase with test payment', async ({ page }) => {
9+
// Initialize page objects
10+
const homePage = new HomePage(page);
11+
const productPage = new ProductPage(page);
12+
const cartDrawer = new CartDrawer(page);
13+
const checkoutPage = new CheckoutPage(page);
14+
15+
// Step 1: Visit the home page
16+
await homePage.goto();
17+
18+
// Step 2: Add a product to cart from home page
19+
await homePage.addToCart();
20+
21+
// Step 3: Open the cart drawer
22+
await homePage.openCartDrawer();
23+
24+
// Step 4: Verify cart drawer is open and has items
25+
await cartDrawer.waitForDrawerOpen();
26+
await cartDrawer.verifyItemInCart();
27+
28+
// Step 5: Proceed to checkout
29+
await cartDrawer.proceedToCheckout();
30+
31+
// Step 6: Verify we're on the checkout page
32+
await expect(page).toHaveURL(/\/checkout/);
33+
34+
// Step 7: Fill in the address form
35+
await checkoutPage.fillAddressForm({
36+
email: 'test@example.com',
37+
firstName: 'Test',
38+
lastName: 'User',
39+
address: '123 Test St',
40+
city: 'Test City',
41+
state: 'TX',
42+
zip: '12345',
43+
phone: '555-123-4567'
44+
});
45+
46+
// Step 8: Select test payment option
47+
await checkoutPage.selectTestPayment();
48+
49+
// Step 9: Complete checkout with test payment
50+
await checkoutPage.completeCheckout();
51+
52+
// Step 10: Verify successful checkout (this will depend on your app's behavior)
53+
// For example, you might check for a success message or redirection to a confirmation page
54+
await expect(page).toHaveURL(/\/confirmation|\/success|\/thank-you/);
55+
});
56+
57+
test('should add product from PDP and complete checkout', async ({ page }) => {
58+
// Initialize page objects
59+
const productPage = new ProductPage(page);
60+
const cartDrawer = new CartDrawer(page);
61+
const checkoutPage = new CheckoutPage(page);
62+
63+
// Step 1: Visit a product detail page (replace with an actual product handle)
64+
await productPage.goto('sample-product');
65+
66+
// Step 2: Add the product to cart
67+
await productPage.addToCart();
68+
69+
// Step 3: Open the cart drawer
70+
await productPage.openCartDrawer();
71+
72+
// Step 4: Verify cart drawer is open and has items
73+
await cartDrawer.waitForDrawerOpen();
74+
await cartDrawer.verifyItemInCart();
75+
76+
// Step 5: Proceed to checkout
77+
await cartDrawer.proceedToCheckout();
78+
79+
// Step 6: Verify we're on the checkout page
80+
await expect(page).toHaveURL(/\/checkout/);
81+
82+
// Step 7: Fill in the address form
83+
await checkoutPage.fillAddressForm({
84+
email: 'test@example.com',
85+
firstName: 'Test',
86+
lastName: 'User',
87+
address: '123 Test St',
88+
city: 'Test City',
89+
state: 'TX',
90+
zip: '12345',
91+
phone: '555-123-4567'
92+
});
93+
94+
// Step 8: Select test payment option
95+
await checkoutPage.selectTestPayment();
96+
97+
// Step 9: Complete checkout with test payment
98+
await checkoutPage.completeCheckout();
99+
100+
// Step 10: Verify successful checkout
101+
await expect(page).toHaveURL(/\/confirmation|\/success|\/thank-you/);
102+
});
103+
});
104+

apps/e2e/tsconfig.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es2019",
4+
"module": "commonjs",
5+
"moduleResolution": "node",
6+
"esModuleInterop": true,
7+
"sourceMap": true,
8+
"outDir": "dist",
9+
"baseUrl": ".",
10+
"strict": true,
11+
"lib": ["esnext", "dom"]
12+
},
13+
"include": ["**/*.ts"]
14+
}
15+

0 commit comments

Comments
 (0)