Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions apps/e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# E2E Testing with Playwright

This package contains end-to-end tests for the application using Playwright.

## Directory Structure

```
apps/e2e/
├── fixtures/ # Test fixtures and setup
├── page-objects/ # Page Object Models
├── tests/ # Test files
├── playwright.config.ts # Playwright configuration
└── package.json # Package configuration
```

## Running Tests

From the root of the monorepo:

```bash
# Run all tests
yarn workspace e2e test

# Run tests with UI
yarn workspace e2e test:ui

# Run tests in debug mode
yarn workspace e2e test:debug

# View test report
yarn workspace e2e report
```

## Test Flow

The tests cover the following flow:

1. Visit the home page or product detail page
2. Add a product to the cart
3. Open the cart drawer
4. Verify the cart contains the added item
5. Proceed to checkout
6. Fill in the address form
7. Select the test payment option
8. Complete the checkout
9. Verify successful checkout

## Page Objects

- `HomePage`: Handles interactions on the home page
- `ProductPage`: Handles interactions on the product detail page
- `CartDrawer`: Handles interactions with the cart drawer
- `CheckoutPage`: Handles interactions on the checkout page

## Customizing Tests

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.

9 changes: 9 additions & 0 deletions apps/e2e/fixtures/test-setup.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Use require instead of import to avoid TypeScript errors
const { test: base, expect } = require('@playwright/test');

// Define a fixture that sets up the test environment
export const test = base.extend({
// Add any global setup here if needed
});

export { expect };
12 changes: 12 additions & 0 deletions apps/e2e/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "e2e",
"version": "1.0.0",
"private": true,
"scripts": {
"install-playwright": "npx playwright@1.40.0 install --with-deps",
"test": "npx playwright@1.40.0 test",
"test:ui": "npx playwright@1.40.0 test --ui",
"test:debug": "npx playwright@1.40.0 test --debug",
"report": "npx playwright@1.40.0 show-report"
}
}
28 changes: 28 additions & 0 deletions apps/e2e/page-objects/cart-drawer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Page, Locator } from '@playwright/test';

export class CartDrawer {
readonly page: Page;
readonly cartDrawer: Locator;
readonly checkoutButton: Locator;
readonly cartItems: Locator;

constructor(page: Page) {
this.page = page;
this.cartDrawer = page.locator('[data-testid="cart-drawer"]');
this.checkoutButton = page.locator('[data-testid="checkout-button"]');
this.cartItems = page.locator('[data-testid="cart-item"]');
}

async waitForDrawerOpen() {
await this.cartDrawer.waitFor({ state: 'visible' });
}

async proceedToCheckout() {
await this.checkoutButton.click();
}

async verifyItemInCart() {
await this.cartItems.first().waitFor({ state: 'visible' });
}
}

58 changes: 58 additions & 0 deletions apps/e2e/page-objects/checkout-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Page, Locator } from '@playwright/test';

export class CheckoutPage {
readonly page: Page;
readonly emailInput: Locator;
readonly firstNameInput: Locator;
readonly lastNameInput: Locator;
readonly addressInput: Locator;
readonly cityInput: Locator;
readonly stateInput: Locator;
readonly zipInput: Locator;
readonly phoneInput: Locator;
readonly testPaymentOption: Locator;
readonly checkoutButton: Locator;

constructor(page: Page) {
this.page = page;
this.emailInput = page.locator('[data-testid="email"]');
this.firstNameInput = page.locator('[data-testid="firstName"]');
this.lastNameInput = page.locator('[data-testid="lastName"]');
this.addressInput = page.locator('[data-testid="address"]');
this.cityInput = page.locator('[data-testid="city"]');
this.stateInput = page.locator('[data-testid="state"]');
this.zipInput = page.locator('[data-testid="zip"]');
this.phoneInput = page.locator('[data-testid="phone"]');
this.testPaymentOption = page.locator('[data-testid="test-payment"]');
this.checkoutButton = page.locator('[data-testid="checkout-test-payment-button"]');
}

async fillAddressForm(customerInfo: {
email: string;
firstName: string;
lastName: string;
address: string;
city: string;
state: string;
zip: string;
phone: string;
}) {
await this.emailInput.fill(customerInfo.email);
await this.firstNameInput.fill(customerInfo.firstName);
await this.lastNameInput.fill(customerInfo.lastName);
await this.addressInput.fill(customerInfo.address);
await this.cityInput.fill(customerInfo.city);
await this.stateInput.fill(customerInfo.state);
await this.zipInput.fill(customerInfo.zip);
await this.phoneInput.fill(customerInfo.phone);
}

async selectTestPayment() {
await this.testPaymentOption.click();
}

async completeCheckout() {
await this.checkoutButton.click();
}
}

26 changes: 26 additions & 0 deletions apps/e2e/page-objects/home-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Page, Locator } from '@playwright/test';

export class HomePage {
readonly page: Page;
readonly addToCartButton: Locator;
readonly cartDrawerButton: Locator;

constructor(page: Page) {
this.page = page;
this.addToCartButton = page.locator('[data-testid="add-to-cart"]');
this.cartDrawerButton = page.locator('[data-testid="cart-button"]');
}

async goto() {
await this.page.goto('/');
}

async addToCart() {
await this.addToCartButton.click();
}

async openCartDrawer() {
await this.cartDrawerButton.click();
}
}

26 changes: 26 additions & 0 deletions apps/e2e/page-objects/product-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Page, Locator } from '@playwright/test';

export class ProductPage {
readonly page: Page;
readonly addToCartButton: Locator;
readonly cartDrawerButton: Locator;

constructor(page: Page) {
this.page = page;
this.addToCartButton = page.locator('[data-testid="add-to-cart"]');
this.cartDrawerButton = page.locator('[data-testid="cart-button"]');
}

async goto(productHandle: string) {
await this.page.goto(`/products/${productHandle}`);
}

async addToCart() {
await this.addToCartButton.click();
}

async openCartDrawer() {
await this.cartDrawerButton.click();
}
}

41 changes: 41 additions & 0 deletions apps/e2e/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { defineConfig, devices } from '@playwright/test';

// Use require instead of import to avoid TypeScript errors
// when @playwright/test is not in node_modules
const { defineConfig, devices } = require('@playwright/test');

module.exports = defineConfig({
testDir: './tests',
timeout: 60000,
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html'],
['list']
],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'on-first-retry',
headless: !!process.env.CI,
viewport: { width: 1280, height: 720 },
actionTimeout: 15000,
navigationTimeout: 30000,
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
// In CI, we'll skip starting the web server and assume it's already running
webServer: process.env.CI ? undefined : {
command: 'cd ../.. && yarn dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120000,
},
});
104 changes: 104 additions & 0 deletions apps/e2e/tests/checkout-flow.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { test, expect } from '../fixtures/test-setup.fixture';
import { HomePage } from '../page-objects/home-page';
import { ProductPage } from '../page-objects/product-page';
import { CartDrawer } from '../page-objects/cart-drawer';
import { CheckoutPage } from '../page-objects/checkout-page';

test.describe('Complete Checkout Flow', () => {
test('should complete a purchase with test payment', async ({ page }) => {
// Initialize page objects
const homePage = new HomePage(page);
const productPage = new ProductPage(page);
const cartDrawer = new CartDrawer(page);
const checkoutPage = new CheckoutPage(page);

// Step 1: Visit the home page
await homePage.goto();

// Step 2: Add a product to cart from home page
await homePage.addToCart();

// Step 3: Open the cart drawer
await homePage.openCartDrawer();

// Step 4: Verify cart drawer is open and has items
await cartDrawer.waitForDrawerOpen();
await cartDrawer.verifyItemInCart();

// Step 5: Proceed to checkout
await cartDrawer.proceedToCheckout();

// Step 6: Verify we're on the checkout page
await expect(page).toHaveURL(/\/checkout/);

// Step 7: Fill in the address form
await checkoutPage.fillAddressForm({
email: 'test@example.com',
firstName: 'Test',
lastName: 'User',
address: '123 Test St',
city: 'Test City',
state: 'TX',
zip: '12345',
phone: '555-123-4567'
});

// Step 8: Select test payment option
await checkoutPage.selectTestPayment();

// Step 9: Complete checkout with test payment
await checkoutPage.completeCheckout();

// Step 10: Verify successful checkout (this will depend on your app's behavior)
// For example, you might check for a success message or redirection to a confirmation page
await expect(page).toHaveURL(/\/confirmation|\/success|\/thank-you/);
});

test('should add product from PDP and complete checkout', async ({ page }) => {
// Initialize page objects
const productPage = new ProductPage(page);
const cartDrawer = new CartDrawer(page);
const checkoutPage = new CheckoutPage(page);

// Step 1: Visit a product detail page (replace with an actual product handle)
await productPage.goto('sample-product');

// Step 2: Add the product to cart
await productPage.addToCart();

// Step 3: Open the cart drawer
await productPage.openCartDrawer();

// Step 4: Verify cart drawer is open and has items
await cartDrawer.waitForDrawerOpen();
await cartDrawer.verifyItemInCart();

// Step 5: Proceed to checkout
await cartDrawer.proceedToCheckout();

// Step 6: Verify we're on the checkout page
await expect(page).toHaveURL(/\/checkout/);

// Step 7: Fill in the address form
await checkoutPage.fillAddressForm({
email: 'test@example.com',
firstName: 'Test',
lastName: 'User',
address: '123 Test St',
city: 'Test City',
state: 'TX',
zip: '12345',
phone: '555-123-4567'
});

// Step 8: Select test payment option
await checkoutPage.selectTestPayment();

// Step 9: Complete checkout with test payment
await checkoutPage.completeCheckout();

// Step 10: Verify successful checkout
await expect(page).toHaveURL(/\/confirmation|\/success|\/thank-you/);
});
});

16 changes: 16 additions & 0 deletions apps/e2e/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es2019",
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"strict": true,
"lib": ["esnext", "dom"],
"allowJs": true,
"checkJs": false
},
"include": ["**/*.ts", "**/*.js"]
}
Loading