Skip to content

Commit a6677d8

Browse files
author
prasanna.kurawadi
committed
chore: initial commit — Playwright automation framework
Add project skeleton: tests, pages, locators, Playwright config, GitHub Actions workflow, and package manifest. - Page Object Model for UI tests - Example UI test (SauceDemo) - Test data and locators - CI workflow to run Playwright tests and upload reports
1 parent 2608ebc commit a6677d8

File tree

9 files changed

+231
-0
lines changed

9 files changed

+231
-0
lines changed

.github/workflows/playwright.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Playwright Tests
2+
on:
3+
push:
4+
branches: [ main, master ]
5+
pull_request:
6+
branches: [ main, master ]
7+
jobs:
8+
test:
9+
timeout-minutes: 60
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- uses: actions/setup-node@v4
14+
with:
15+
node-version: lts/*
16+
- name: Install dependencies
17+
run: npm ci
18+
- name: Install Playwright Browsers
19+
run: npx playwright install --with-deps
20+
- name: Run Playwright tests
21+
run: npx playwright test
22+
- uses: actions/upload-artifact@v4
23+
if: ${{ !cancelled() }}
24+
with:
25+
name: playwright-report
26+
path: playwright-report/
27+
retention-days: 30

.gitignore

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

locators/login.locators.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// locators/login.locators.ts
2+
3+
export class LoginLocators {
4+
// Input fields
5+
static readonly USERNAME_INPUT = '[data-test="username"]';
6+
static readonly PASSWORD_INPUT = '[data-test="password"]';
7+
8+
// Buttons
9+
static readonly LOGIN_BUTTON = '[data-test="login-button"]';
10+
11+
// Error messages
12+
static readonly ERROR_MESSAGE = '[data-test="error"]';
13+
static readonly ERROR_CLOSE_BUTTON = '.error-button';
14+
15+
// Post-login elements (for verification)
16+
static readonly INVENTORY_CONTAINER = '.inventory_container';
17+
static readonly APP_LOGO = '.app_logo';
18+
static readonly SHOPPING_CART = '.shopping_cart_link';
19+
}

package.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"name": "playwright-automation-framework",
3+
"version": "1.0.0",
4+
"description": "Production-ready Playwright test automation framework with SauceDemo App. Features separated locators, Page Object Model, UI testing.",
5+
"scripts": {
6+
"test": "npx playwright test",
7+
"test:ui": "npx playwright test tests/ui",
8+
"test:headed": "npx playwright test --headed",
9+
"test:chrome": "npx playwright test --project=chrome",
10+
"report": "npx playwright show-report",
11+
"trace": "npx playwright show-trace"
12+
},
13+
"repository": {
14+
"type": "git",
15+
"url": "git+https://github.com/kurawadiprasanna/playwright-automation-framework.git"
16+
},
17+
"keywords": [
18+
"playwright",
19+
"automation",
20+
"testing",
21+
"e2e",
22+
"api-testing",
23+
"typescript",
24+
"conduit",
25+
"test-automation",
26+
"page-object-model",
27+
"ci-cd",
28+
"quality-assurance",
29+
"selenium-alternative"
30+
],
31+
"author": "Prasanna Kurawadi",
32+
"license": "MIT",
33+
"bugs": {
34+
"url": "https://github.com/kurawadiprasanna/playwright-automation-framework/issues"
35+
},
36+
"homepage": "https://github.com/kurawadiprasanna/playwright-automation-framework#readme",
37+
"devDependencies": {
38+
"@faker-js/faker": "^10.0.0",
39+
"@playwright/test": "^1.55.1",
40+
"@types/node": "^24.6.2",
41+
"typescript": "^4.9.5"
42+
}
43+
}

pages/Login.Page.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Page, Locator } from '@playwright/test';
2+
import { LoginLocators } from '../locators/login.locators';
3+
4+
export class LoginPage {
5+
readonly page: Page;
6+
readonly usernameInput: Locator;
7+
readonly passwordInput: Locator;
8+
readonly signInButton: Locator;
9+
readonly errorMessage: Locator;
10+
readonly appLogo: Locator;
11+
readonly shoppingCart: Locator;
12+
13+
constructor(page: Page) {
14+
this.page = page;
15+
this.usernameInput = page.locator(LoginLocators.USERNAME_INPUT);
16+
this.passwordInput = page.locator(LoginLocators.PASSWORD_INPUT);
17+
this.signInButton = page.locator(LoginLocators.LOGIN_BUTTON);
18+
this.errorMessage = page.locator(LoginLocators.ERROR_MESSAGE);
19+
this.appLogo = page.locator(LoginLocators.APP_LOGO);
20+
this.shoppingCart = page.locator(LoginLocators.SHOPPING_CART);
21+
}
22+
23+
async navigate() {
24+
// Sauce Demo's login page is the app root
25+
await this.page.goto('/');
26+
}
27+
28+
async login(username: string, password: string) {
29+
await this.usernameInput.fill(username);
30+
await this.passwordInput.fill(password);
31+
await this.signInButton.click();
32+
}
33+
34+
async getErrorMessage() {
35+
return await this.errorMessage.textContent();
36+
}
37+
}

pages/tsconfig.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2020",
4+
"module": "commonjs",
5+
"lib": ["ES2020"],
6+
"moduleResolution": "node",
7+
"strict": true,
8+
"esModuleInterop": true,
9+
"skipLibCheck": true,
10+
"forceConsistentCasingInFileNames": true,
11+
"resolveJsonModule": true,
12+
"types": ["node", "@playwright/test"]
13+
},
14+
"include": [
15+
"**/*.ts"
16+
],
17+
"exclude": [
18+
"node_modules",
19+
"test-results",
20+
"playwright-report"
21+
]
22+
}

playwright.config.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { defineConfig, devices } from '@playwright/test';
2+
3+
export default defineConfig({
4+
testDir: './tests',
5+
fullyParallel: true,
6+
forbidOnly: !!process.env.CI,
7+
retries: process.env.CI ? 2 : 0,
8+
workers: process.env.CI ? 1 : undefined,
9+
reporter: [
10+
['html'],
11+
['list'],
12+
['junit', { outputFile: 'test-results/junit.xml' }]
13+
],
14+
15+
use: {
16+
baseURL: 'https://www.saucedemo.com/',
17+
trace: 'on-first-retry',
18+
screenshot: 'only-on-failure',
19+
video: 'retain-on-failure',
20+
},
21+
22+
projects: [
23+
{
24+
name: 'chromium',
25+
use: { ...devices['Desktop Chrome'] },
26+
},
27+
{
28+
name: 'firefox',
29+
use: { ...devices['Desktop Firefox'] },
30+
},
31+
{
32+
name: 'webkit',
33+
use: { ...devices['Desktop Safari'] },
34+
},
35+
],
36+
});

test-data/login-testdata.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"validUsers": [
3+
{
4+
"username": "standard_user",
5+
"password": "secret_sauce",
6+
"description": "standard user",
7+
"expectedPostLogin": {
8+
"appLogoText": "Swag Labs",
9+
"urlPattern": "inventory.html"
10+
}
11+
}
12+
]
13+
}

tests/ui/auth/login.spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// tests/login-json.spec.ts
2+
3+
import { test, expect } from '@playwright/test';
4+
import { LoginPage } from '../../../pages/Login.Page';
5+
import testData from '../../../test-data/login-testdata.json';
6+
7+
test.describe('SauceDemo Login Tests - JSON Data Driven', () => {
8+
let loginPage: LoginPage;
9+
10+
test.beforeEach(async ({ page }) => {
11+
loginPage = new LoginPage(page);
12+
await loginPage.navigate();
13+
});
14+
15+
test.describe('Successful Login Tests', () => {
16+
for (const user of testData.validUsers) {
17+
test(`should successfully login with ${user.description}`, async ({ page }) => {
18+
// login with provided credentials
19+
await loginPage.login(user.username, user.password);
20+
await expect(loginPage.appLogo).toHaveText(user.expectedPostLogin.appLogoText);
21+
await expect(page).toHaveURL(new RegExp(user.expectedPostLogin.urlPattern));
22+
});
23+
}
24+
});
25+
});

0 commit comments

Comments
 (0)