diff --git a/.gitignore b/.gitignore index 008be21..9cb1ff4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,31 @@ -/target/ -.project -.classpath -/.settings/ -/ExtentReports/ -logfile.log -/test-output/ - +# === Legacy Selenium/Maven artifacts === +/target/ +.project +.classpath +/.settings/ +/ExtentReports/ +logfile.log +/test-output/ + +# === Node.js / Playwright === +node_modules/ +dist/ +test-results/ +playwright-report/ +blob-report/ +.playwright/ + +# === IDE === +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# === OS === +.DS_Store +Thumbs.db + +# === Environment === +.env +.env.local diff --git a/MIGRATION_PLAN.md b/MIGRATION_PLAN.md new file mode 100644 index 0000000..147393d --- /dev/null +++ b/MIGRATION_PLAN.md @@ -0,0 +1,230 @@ +# Selenium → Playwright Migration Plan + +## 1. Executive Summary + +This document outlines the migration strategy for converting the existing Selenium + TestNG (Java/Maven) test automation framework to Playwright (TypeScript). The current framework follows a Page Object Model pattern with Extent Reports, Log4j logging, and email notifications. + +--- + +## 2. Current Framework Inventory + +### 2.1 Page Object Classes + +| Class | Location | Elements | Methods | +|-------|----------|----------|---------| +| `BasePage` | `pages/BasePage.java` | — | Constructor: initializes `PageFactory`, creates `FluentWait` (10s timeout, 2s polling, ignoring `NoSuchElementException`/`WebDriverException`) | +| `GooglePage` | `pages/GooglePage.java` | `searchinput` (`@FindBy(name="q")`) | `searchText(key)` — types text + ENTER | +| `FacebookLoginPage` | `pages/FacebookLoginPage.java` | `emailInput` (`@FindBy(id="email")`), `pass` (`@FindBy(id="pass")`) | `enterEmail(email)`, `enterPassword(password)`, `clickSignIn()` — fluent API returning `this` | + +### 2.2 Test Classes + +| Class | Annotation | Tests | Description | +|-------|-----------|-------|-------------| +| `BaseTest` | `@Listeners({ReportListener, LogListener})` | — | `@BeforeSuite`: load properties, log start. `@AfterSuite`: log stats, send mail. `@BeforeClass`: ChromeDriver setup (headless, WebDriverManager). `@AfterClass`: driver cleanup. | +| `GoogleSearchTest` | `@Test(testName="Google search test")` | `googleSearchTest()` — navigate to Google, search "abc", assert title contains "abc" | +| `FaceBookLoginTest` | `@Test(testName="Facebook login test")` | `facebookLoginTest()` — navigate to Facebook, enter credentials, click sign in, `Assert.assertTrue(false)` (intentional failure) | + +### 2.3 TestNG Configuration (`testng.xml`) + +- Suite with `parallel="classes"`, `thread-count="5"` +- Two test classes registered + +### 2.4 Utility & Infrastructure Classes + +| Class | Purpose | +|-------|---------| +| `WebDriverContext` | `InheritableThreadLocal` — thread-safe driver instance storage | +| `Constants` | File path constants (report dir, config paths, driver path) | +| `PageinstancesFactory` | Reflective factory creating Page Object instances via `WebDriverContext.getDriver()` | +| `LoggerUtil` | Log4j wrapper (`log()`, `getLogger()`) | +| `MailUtil` | Email report sender via SimpleJavaMail (SMTP/TLS, HTML body with pass/fail stats) | +| `ReportUtil` | Screenshot capture (Base64) + ExtentReports logging | +| `TestProperties` | Java Properties file loader for `test.properties` | + +### 2.5 Integrations + +| Integration | Library | Usage | +|-------------|---------|-------| +| **Extent Reports** | `com.relevantcodes:extentreports:2.41.2` | HTML report generation with screenshots, managed by `ExtentReportManager` and `ReportListener` | +| **Log4j** | `log4j:log4j:1.2.17` | File-based logging to `logfile.log` | +| **SimpleJavaMail** | `org.simplejavamail:simple-java-mail:5.1.1` | Email notifications with SMTP/TLS, toggled via `mail.sendmail` property | +| **WebDriverManager** | `io.github.bonigarcia:webdrivermanager:5.9.2` | Automatic ChromeDriver binary management | +| **Postgres / ALM** | — | **Not present** in this codebase | + +### 2.6 Dependencies (pom.xml) + +- Selenium Java 4.25.0 +- TestNG 6.14.3 +- ExtentReports 2.41.2 +- Log4j 1.2.17 +- SimpleJavaMail 5.1.1 +- WebDriverManager 5.9.2 +- Maven Surefire Plugin 2.19.1 + +--- + +## 3. Language Decision: Playwright TypeScript + +### Decision: **Playwright TypeScript** + +### Rationale + +| Factor | Playwright Java | Playwright TypeScript | +|--------|----------------|----------------------| +| **Maturity** | Stable but follows TS releases | First-class, most mature | +| **Test Runner** | Requires JUnit/TestNG integration | Built-in `@playwright/test` with fixtures, parallelism, retries | +| **Reporting** | Manual setup needed | Built-in HTML reporter, trace viewer, video recording | +| **Auto-Wait** | Available | Available + tightest integration | +| **Community** | Smaller | Largest ecosystem, most examples | +| **Codegen** | Available | Available + best integration | +| **Tooling** | Maven/Gradle | npm/pnpm — faster iteration, simpler CI | +| **Framework Features** | Need to wire reporters, parallel config | All included out of the box | + +**Key justification:** The existing Java framework's complexity (WebDriverContext, PageinstancesFactory, ExtentReportManager, ReportListener, LogListener) exists to work around Selenium/TestNG limitations. Playwright's TypeScript test runner eliminates the need for all of these — fixtures handle driver lifecycle, built-in reporters replace ExtentReports, auto-wait replaces FluentWait, and the test runner handles parallelism natively. The migration to TypeScript results in significantly less code to maintain. + +--- + +## 4. Component-by-Component Migration Strategy + +### 4.1 BasePage → Base Page Object + +| Selenium (Java) | Playwright (TypeScript) | Risk | +|-----------------|------------------------|------| +| `PageFactory.initElements()` | Not needed — Playwright locators are lazy | Low | +| `FluentWait` (10s, 2s polling) | Playwright auto-wait (built-in) | Low | +| `WebDriver` field | `Page` field from Playwright | Low | + +**Risk: Low** — Direct pattern mapping, Playwright's auto-wait is strictly superior. + +### 4.2 Page Object Classes (GooglePage, FacebookLoginPage) + +| Selenium Pattern | Playwright Equivalent | Risk | +|-----------------|----------------------|------| +| `@FindBy(name="q")` | `this.page.locator('[name="q"]')` or `this.page.getByRole()` | Low | +| `@FindBy(id="email")` | `this.page.locator('#email')` | Low | +| `element.sendKeys(text + Keys.ENTER)` | `locator.fill(text)` + `locator.press('Enter')` | Low | +| `element.submit()` | `locator.click()` on submit button or `press('Enter')` | Low | +| Fluent API (`return this`) | Same pattern preserved | Low | + +**Risk: Low** — Locator strategies map 1:1. Playwright locators are more robust. + +### 4.3 Test Runner: TestNG → Playwright Test + +| TestNG Feature | Playwright Test Equivalent | Risk | +|---------------|---------------------------|------| +| `@BeforeSuite` / `@AfterSuite` | `globalSetup` / `globalTeardown` in config | Low | +| `@BeforeClass` / `@AfterClass` | Fixtures (`beforeAll` / `afterAll`) or custom fixtures | Low | +| `@Test` annotation | `test()` function | Low | +| `parallel="classes"` | `fullyParallel: true` in config | Low | +| `Assert.assertTrue()` | `expect()` from `@playwright/test` | Low | +| `@Listeners` | Reporter config in `playwright.config.ts` | Low | + +**Risk: Low** — Playwright Test provides superset of TestNG capabilities. + +### 4.4 WebDriverContext (ThreadLocal) → Eliminated + +Playwright Test's fixture system manages browser/page lifecycle per test worker. No manual thread-local management needed. + +**Risk: Low** — Complete elimination of boilerplate. + +### 4.5 PageinstancesFactory → Eliminated + +Page Objects instantiated directly with `new PageClass(page)` in tests. Reflection-based factory is unnecessary. + +**Risk: Low** — Simpler, type-safe instantiation. + +### 4.6 ExtentReports → Playwright HTML Reporter + +| Extent Feature | Playwright Equivalent | Risk | +|---------------|----------------------|------| +| HTML report | `html` reporter (built-in) | Low | +| Screenshots on pass/fail | `screenshot: 'on'` config option | Low | +| Custom config XML | Reporter options in `playwright.config.ts` | Low | +| Thread-safe report manager | Not needed — reporter is built-in | Low | + +**Risk: Low** — Playwright HTML reporter is more feature-rich than ExtentReports v2. + +### 4.7 Log4j → Console + Trace Viewer + +| Log4j Feature | Playwright Equivalent | Risk | +|--------------|----------------------|------| +| File-based logging | Trace viewer (`trace: 'on-first-retry'`) | Low | +| Log levels | `console.log()` + Playwright's built-in test output | Low | + +**Risk: Low** — Trace viewer provides far richer debugging than log files. + +### 4.8 SimpleJavaMail → Post-test CI Integration + +| Mail Feature | Playwright Equivalent | Risk | +|-------------|----------------------|------| +| SMTP email with stats | CI pipeline notification (GitHub Actions, etc.) | Medium | +| Configurable via properties | Environment variables or config file | Low | + +**Risk: Medium** — Email sending is typically handled at CI level, not framework level. A placeholder utility will be provided showing how to integrate with CI notification systems. + +--- + +## 5. Migration Phases + +### Phase 1: Framework Setup +1. Initialize Node.js project with TypeScript +2. Install Playwright and configure browsers +3. Create `playwright.config.ts` with parallel execution, reporters, and browser configs +4. Set up project directory structure + +### Phase 2: Page Object Migration +1. Create `BasePage` class with Playwright `Page` reference +2. Migrate `GooglePage` with Playwright locators +3. Migrate `FacebookLoginPage` with Playwright locators + +### Phase 3: Test Migration +1. Convert `GoogleSearchTest` to Playwright test spec +2. Convert `FaceBookLoginTest` to Playwright test spec + +### Phase 4: Optimization +1. Configure trace collection +2. Add screenshot-on-failure +3. Set up cross-browser testing (Chromium, Firefox, WebKit) +4. Configure parallel execution + +### Phase 5: Documentation +1. Create `MIGRATION_RUNBOOK.md` +2. Update `README.md` + +--- + +## 6. Recommended Project Structure + +``` +ts-selenium-simple/ +├── playwright.config.ts # Playwright configuration +├── package.json # Node.js dependencies +├── tsconfig.json # TypeScript configuration +├── tests/ +│ ├── google-search.spec.ts # Google search test +│ └── facebook-login.spec.ts # Facebook login test +├── pages/ +│ ├── base.page.ts # Base page object +│ ├── google.page.ts # Google page object +│ └── facebook-login.page.ts # Facebook login page object +├── MIGRATION_PLAN.md # This document +├── MIGRATION_RUNBOOK.md # Migration runbook +└── README.md # Updated documentation +``` + +--- + +## 7. Risk Summary + +| Component | Risk Level | Mitigation | +|-----------|-----------|------------| +| Page Objects | Low | 1:1 locator mapping | +| Test Runner | Low | Playwright Test has superset features | +| Assertions | Low | `expect()` is more powerful than TestNG assertions | +| Waits | Low | Auto-wait eliminates all explicit/implicit waits | +| Reporting | Low | Built-in HTML reporter is superior | +| Logging | Low | Trace viewer replaces file logging | +| Email Notifications | Medium | Handled at CI level; placeholder provided | +| Parallel Execution | Low | Native in Playwright Test | + +**Overall Risk: Low** — The existing framework is straightforward with no complex integrations (no DB, no ALM). All components have direct or superior Playwright equivalents. diff --git a/MIGRATION_RUNBOOK.md b/MIGRATION_RUNBOOK.md new file mode 100644 index 0000000..8f7382d --- /dev/null +++ b/MIGRATION_RUNBOOK.md @@ -0,0 +1,275 @@ +# Migration Runbook: Selenium + TestNG → Playwright + +## 1. Pattern Conversion Reference + +### 1.1 Locator Strategies + +| Selenium (Java) | Playwright (TypeScript) | Notes | +|-----------------|------------------------|-------| +| `@FindBy(id = "email")` | `page.locator('#email')` | CSS selector | +| `@FindBy(name = "q")` | `page.locator('[name="q"]')` | Attribute selector | +| `@FindBy(className = "btn")` | `page.locator('.btn')` | CSS class | +| `@FindBy(xpath = "//div")` | `page.locator('//div')` | XPath (supported but discouraged) | +| `@FindBy(css = "div.class")` | `page.locator('div.class')` | CSS selector | +| `@FindBy(linkText = "Click")` | `page.getByRole('link', { name: 'Click' })` | Prefer role-based | +| `@FindBy(tagName = "input")` | `page.locator('input')` | Tag name | +| `driver.findElement(By.id("x"))` | `page.locator('#x')` | Inline locator | + +**Best practice:** Prefer Playwright's user-facing locators (`getByRole`, `getByText`, `getByLabel`, `getByPlaceholder`) over CSS/XPath selectors for more resilient tests. + +### 1.2 Element Interactions + +| Selenium | Playwright | Notes | +|----------|-----------|-------| +| `element.sendKeys("text")` | `locator.fill("text")` | `fill()` clears first, unlike `sendKeys` | +| `element.sendKeys(Keys.ENTER)` | `locator.press('Enter')` | Key name strings | +| `element.click()` | `locator.click()` | Auto-waits for element | +| `element.submit()` | `locator.press('Enter')` or `submitButton.click()` | No direct `submit()` — use explicit action | +| `element.clear()` | `locator.clear()` | Or `locator.fill('')` | +| `element.getText()` | `locator.textContent()` | Or `locator.innerText()` | +| `element.getAttribute("x")` | `locator.getAttribute('x')` | Same concept | +| `element.isDisplayed()` | `locator.isVisible()` | Or use `expect(locator).toBeVisible()` | + +### 1.3 Navigation & Browser + +| Selenium | Playwright | Notes | +|----------|-----------|-------| +| `driver.get(url)` | `page.goto(url)` | Returns Response object | +| `driver.getTitle()` | `page.title()` | Async | +| `driver.getCurrentUrl()` | `page.url()` | Synchronous property | +| `driver.navigate().back()` | `page.goBack()` | — | +| `driver.navigate().forward()` | `page.goForward()` | — | +| `driver.navigate().refresh()` | `page.reload()` | — | +| `driver.manage().window().maximize()` | Config: `viewport: { width: 1280, height: 720 }` | Set in config or per-test | + +### 1.4 Waits + +| Selenium | Playwright | Notes | +|----------|-----------|-------| +| `driver.manage().timeouts().implicitlyWait(10, SECONDS)` | **Not needed** | Playwright auto-waits | +| `new FluentWait<>(driver).withTimeout(10s).pollingEvery(2s)` | **Not needed** | Auto-wait with configurable `actionTimeout` | +| `new WebDriverWait(driver, 10).until(ExpectedConditions.visibilityOf(el))` | `await locator.waitFor({ state: 'visible' })` | Explicit when needed | +| `ExpectedConditions.elementToBeClickable(el)` | **Not needed** | Auto-wait ensures clickability | +| `Thread.sleep(ms)` | `await page.waitForTimeout(ms)` | **Avoid** — use proper waits | +| — | `await page.waitForLoadState('networkidle')` | Wait for network to settle | +| — | `await page.waitForURL(/pattern/)` | Wait for URL change | + +**Key insight:** Playwright's auto-wait is the single biggest improvement. It automatically waits for elements to be attached, visible, stable, enabled, and not obscured before performing actions. This eliminates 90%+ of wait-related code. + +### 1.5 Assertions + +| TestNG | Playwright | Notes | +|--------|-----------|-------| +| `Assert.assertTrue(condition)` | `expect(value).toBeTruthy()` | Generic boolean | +| `Assert.assertTrue(condition, msg)` | `expect(value, msg).toBeTruthy()` | With custom message | +| `Assert.assertEquals(actual, expected)` | `expect(actual).toBe(expected)` | Strict equality | +| `Assert.assertNotNull(obj)` | `expect(obj).not.toBeNull()` | Null check | +| — | `await expect(page).toHaveTitle(/regex/)` | **Auto-retrying** page assertion | +| — | `await expect(page).toHaveURL(/regex/)` | **Auto-retrying** URL assertion | +| — | `await expect(locator).toBeVisible()` | **Auto-retrying** element assertion | +| — | `await expect(locator).toHaveText('text')` | **Auto-retrying** text assertion | + +**Key insight:** Playwright's `expect` with locators/page auto-retries until timeout. This is fundamentally more reliable than TestNG assertions which check once and fail immediately. + +### 1.6 Test Lifecycle + +| TestNG | Playwright Test | Notes | +|--------|----------------|-------| +| `@BeforeSuite` | `globalSetup` in config | Runs once before all tests | +| `@AfterSuite` | `globalTeardown` in config | Runs once after all tests | +| `@BeforeClass` | `test.beforeAll()` | Runs once per test file | +| `@AfterClass` | `test.afterAll()` | Runs once per test file | +| `@BeforeMethod` | `test.beforeEach()` | Runs before each test | +| `@AfterMethod` | `test.afterEach()` | Runs after each test | +| `@Test` | `test('name', async ({ page }) => { ... })` | Test definition | +| `@Test(enabled = false)` | `test.skip('name', ...)` | Skip test | +| `@Test(groups = "smoke")` | `test.describe('smoke', ...)` or tags | Grouping | +| `@Listeners({...})` | Reporter config in `playwright.config.ts` | Automatic | + +### 1.7 Page Object Pattern + +**Selenium (Java):** +```java +public class MyPage extends BasePage { + @FindBy(id = "element") + private WebElement myElement; + + public MyPage(WebDriver driver) { + super(driver); + } + + public void doAction() { + myElement.click(); + } +} +``` + +**Playwright (TypeScript):** +```typescript +import { type Page } from '@playwright/test'; +import { BasePage } from './base.page.js'; + +export class MyPage extends BasePage { + private readonly myElement = this.page.locator('#element'); + + constructor(page: Page) { + super(page); + } + + async doAction(): Promise { + await this.myElement.click(); + } +} +``` + +**Differences:** +- No `PageFactory.initElements()` — Playwright locators are lazy (evaluated on use) +- No `@FindBy` — locators are regular class properties +- All actions are `async` — Playwright uses promises +- No `WebDriverContext` / `ThreadLocal` — Playwright Test manages browser context per worker + +### 1.8 Test Instantiation + +**Selenium:** +```java +GooglePage page = PageinstancesFactory.getInstance(GooglePage.class); +``` + +**Playwright:** +```typescript +const googlePage = new GooglePage(page); +``` + +The `PageinstancesFactory` reflection-based factory is completely eliminated. Direct construction is type-safe and simpler. + +--- + +## 2. Gotchas and Manual Interventions + +### 2.1 `sendKeys` vs `fill` +- Selenium's `sendKeys` **appends** to existing text; Playwright's `fill` **replaces** it. +- If you need append behavior, use `locator.pressSequentially('text')` instead of `fill`. + +### 2.2 `submit()` Removal +- Selenium has `element.submit()` that submits the enclosing form. +- Playwright has no direct equivalent. Use `button.click()` on the submit button or `input.press('Enter')`. + +### 2.3 Implicit Waits Are Gone +- Remove ALL implicit wait configuration. Playwright auto-waits. +- If tests are "too fast," the page likely hasn't loaded yet — use `await page.waitForLoadState()` or proper locator waits. + +### 2.4 Thread Safety Is Automatic +- `WebDriverContext` (InheritableThreadLocal) is eliminated. +- Playwright Test provides isolated `page` fixtures per test — no manual thread management needed. + +### 2.5 Screenshots Are Automatic +- Remove all manual `TakesScreenshot` code. +- Configure `screenshot: 'only-on-failure'` (or `'on'`) in `playwright.config.ts`. + +### 2.6 Browser Setup Is Automatic +- Remove `WebDriverManager.chromedriver().setup()` and `ChromeOptions` configuration. +- Playwright manages browser binaries via `npx playwright install`. +- Headless is the default; use `--headed` flag for visible browser. + +### 2.7 Assertions Are Auto-Retrying +- TestNG's `Assert.assertTrue(driver.getTitle().contains("abc"))` checks once. +- Playwright's `await expect(page).toHaveTitle(/abc/)` retries until timeout. +- This eliminates most flaky test issues related to timing. + +### 2.8 Cookie/Session Handling +- Selenium: `driver.manage().getCookies()` +- Playwright: `context.cookies()` and `context.addCookies()` +- Storage state can be saved/loaded: `context.storageState({ path: 'state.json' })` + +--- + +## 3. Framework Comparison + +| Aspect | Before (Selenium + TestNG) | After (Playwright) | +|--------|---------------------------|---------------------| +| **Language** | Java 8 | TypeScript (ES2022) | +| **Build Tool** | Maven (pom.xml) | npm (package.json) | +| **Test Runner** | TestNG 6.14.3 | Playwright Test (built-in) | +| **Browser Automation** | Selenium 4.25.0 | Playwright 1.59+ | +| **Browser Management** | WebDriverManager 5.9.2 | Built-in (`npx playwright install`) | +| **Reporting** | ExtentReports 2.41.2 | HTML Reporter (built-in) | +| **Logging** | Log4j 1.2.17 | Trace Viewer + console | +| **Email** | SimpleJavaMail 5.1.1 | CI-level notifications | +| **Wait Strategy** | FluentWait + implicit waits | Auto-wait (zero config) | +| **Parallel Execution** | `testng.xml` config | `fullyParallel: true` | +| **Page Factory** | `PageFactory.initElements()` | Not needed (lazy locators) | +| **Thread Safety** | `InheritableThreadLocal` | Automatic (fixtures) | +| **Screenshot** | Manual (`TakesScreenshot`) | Config-driven (automatic) | +| **Cross-Browser** | Manual driver setup per browser | Config-driven (projects) | +| **Debugging** | Log files + screenshots | Trace viewer + video + step-through | +| **Total Source Files** | 16 Java files + 4 XML configs | 5 TypeScript files + 1 config | +| **Lines of Code (framework)** | ~700 LOC | ~120 LOC | +| **External Dependencies** | 6 (Maven) | 3 (npm: playwright, typescript, @types/node) | + +--- + +## 4. Recommendations for Teams Doing Similar Migrations + +### 4.1 Migration Strategy +1. **Start with the Page Objects** — they map most directly and validate locator strategies. +2. **Convert one test at a time** — verify it passes before moving to the next. +3. **Run old and new suites in parallel** during transition to ensure parity. +4. **Delete legacy infrastructure last** — WebDriverContext, ExtentReportManager, listeners, etc. are not needed but can remain during transition for reference. + +### 4.2 Effort Optimization +- **Auto-wait eliminates the most code.** Every `FluentWait`, `WebDriverWait`, `Thread.sleep`, and implicit wait is deleted with no replacement needed. +- **Built-in reporter eliminates the most files.** ExtentReportManager, ReportListener, ReportUtil, extent-config.xml — all gone, replaced by one config line. +- **Fixtures eliminate WebDriverContext.** All ThreadLocal/driver lifecycle management disappears. +- **PageinstancesFactory is unnecessary.** Direct `new Page(page)` is type-safe and simpler. + +### 4.3 Effort Estimates (by team size) + +| Team Familiarity | Framework Size | Estimated Effort | +|-----------------|----------------|------------------| +| TypeScript-proficient | Small (< 20 tests) | 1-2 days | +| TypeScript-proficient | Medium (20-100 tests) | 1-2 weeks | +| TypeScript-proficient | Large (100+ tests) | 2-4 weeks | +| Java-only, learning TS | Small (< 20 tests) | 3-5 days | +| Java-only, learning TS | Medium (20-100 tests) | 2-4 weeks | +| Java-only, learning TS | Large (100+ tests) | 4-8 weeks | + +### 4.4 Maintainability Guidelines +- **Use role-based locators** (`getByRole`, `getByText`, `getByLabel`) over CSS selectors when possible — they're more resilient to DOM changes. +- **Keep page objects focused** — one page object per page/component, not per test. +- **Use `test.describe` for grouping** — replaces TestNG test groups and suites. +- **Leverage fixtures for shared state** — custom fixtures can replace `@BeforeClass` patterns for complex setup. +- **Use Playwright's codegen** (`npx playwright codegen`) to quickly generate locators for new pages. + +### 4.5 CI/CD Integration +- Playwright's `--reporter=github` outputs annotations for GitHub Actions. +- Use `--reporter=blob` for sharded test runs (merge with `npx playwright merge-reports`). +- Set `retries: 2` in CI to handle flaky external sites. +- Use `workers: 1` in CI for predictable resource usage, or increase for speed. + +--- + +## 5. Files Migrated + +| Original (Selenium/Java) | Migrated (Playwright/TS) | Status | +|--------------------------|--------------------------|--------| +| `pages/BasePage.java` | `pages/base.page.ts` | Migrated | +| `pages/GooglePage.java` | `pages/google.page.ts` | Migrated | +| `pages/FacebookLoginPage.java` | `pages/facebook-login.page.ts` | Migrated | +| `tests/BaseTest.java` | `playwright.config.ts` (config-driven) | Eliminated — replaced by config | +| `tests/GoogleSearchTest.java` | `tests/google-search.spec.ts` | Migrated | +| `tests/FaceBookLoginTest.java` | `tests/facebook-login.spec.ts` | Migrated | +| `context/WebDriverContext.java` | — | Eliminated — Playwright fixtures | +| `context/Constants.java` | — | Eliminated — paths in config | +| `factory/PageinstancesFactory.java` | — | Eliminated — direct construction | +| `listeners/LogListener.java` | — | Eliminated — built-in logging | +| `listeners/ReportListener.java` | — | Eliminated — built-in reporter | +| `report/ExtentReportManager.java` | — | Eliminated — HTML reporter | +| `util/LoggerUtil.java` | — | Eliminated — trace viewer | +| `util/MailUtil.java` | — | Eliminated — CI notifications | +| `util/ReportUtil.java` | — | Eliminated — automatic screenshots | +| `util/TestProperties.java` | — | Eliminated — env vars / config | +| `resources/config/extent-config.xml` | — | Eliminated — reporter config in TS | +| `resources/config/test.properties` | — | Eliminated — env vars | +| `resources/log4j.xml` | — | Eliminated — trace viewer | +| `resources/suites/testng.xml` | `playwright.config.ts` | Migrated — projects & parallel config | +| `pom.xml` | `package.json` | Migrated | diff --git a/README.md b/README.md index 33ab4ca..ccccb78 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,136 @@ -selenium-testng-framework ---- - ---- -A sample framework based on Page Object Model, Selenium, TestNG using Java. - -This framework is based in **Page Object Model (POM).** - -The framework uses: - -1. Java -2. Selenium -3. TestNG -4. ExtentReport -5. Log4j -6. SimpleJavaMail - -Steps to create test cases: ----- -Let's say we want to automate Google search test. - -1.Create GoogleSearchPage in **pages** package. - A page class typically should contain all the elements that are present on the page and corresponding action methods. - - ``` - public class GooglePage extends BasePage { - - @FindBy(name = "q") - private WebElement searchinput; - - public GooglePage(WebDriver driver) { - super(driver); - } - - public void searchText(String key) { - searchinput.sendKeys(key + Keys.ENTER); - } - -} -``` -2.Create the test class which class the methods of GoogleSearchPage - -``` -@Test(testName = "Google search test", description = "Test description") -public class GoogleSearchTest extends BaseTest { - - @Test - public void googleSearchTest() { - driver.get("https://www.google.co.in/"); - GooglePage googlePage = PageinstancesFactory.getInstance(GooglePage.class); - googlePage.searchText("abc"); - Assert.assertTrue(driver.getTitle().contains("abc"), "Title doesn't contain abc : Test Failed"); - } -} -``` -3.Add the test class in testng.xml file under the folder `src/test/resources/suites/` - -``` - - - - - -``` -4.Execute the test cases by maven command `mvn clean test` - ---- - -Reproting ---- -The framework gives report in three ways, - -1. Log - In file `logfile.log`. -2. A html report - Which is generated using extent reports, under the folder `ExtentReports`. -3. A mail report - For which the toggle `mail.sendmail` in `test.properties` should be set `true`. And all the properties such as `smtp host, port, proxy details, etc.,` should be provided correctly. - ---- - -Key Points: ---- - -1. The class `WebDriverContext` is responsible for maintaining the same WebDriver instance throughout the test. So whenever you require a webdriver instance which has been using for current test (In current thread) always call `WebDriverContext.getDriver()`. -2. Always use `PageinstancesFactory.getInstance(type)` to get the instance of particular Page Object. (Of course you can use `new` but it's better use a single approach across the framework. - ---- - ->For any query or suggestions please do comment or mail @ diggavibharathish@gmail.com +# Playwright Test Automation Framework + +A test automation framework based on **Page Object Model** and **Playwright** using TypeScript. + +> **Migrated from:** Selenium + TestNG (Java/Maven). See [MIGRATION_PLAN.md](./MIGRATION_PLAN.md) and [MIGRATION_RUNBOOK.md](./MIGRATION_RUNBOOK.md) for details. + +## Tech Stack + +- [Playwright](https://playwright.dev/) — Browser automation and testing +- [TypeScript](https://www.typescriptlang.org/) — Type-safe JavaScript +- [Playwright Test](https://playwright.dev/docs/test-intro) — Built-in test runner with parallel execution + +## Prerequisites + +- Node.js 18+ +- npm 9+ + +## Setup + +```bash +npm install +npx playwright install --with-deps +``` + +## Running Tests + +```bash +# Run all tests across all browsers +npm test + +# Run tests for a specific browser +npm run test:chromium +npm run test:firefox +npm run test:webkit + +# Run tests in headed mode (visible browser) +npm run test:headed + +# Run tests in debug mode (Playwright Inspector) +npm run test:debug + +# Run tests with Playwright UI mode +npm run test:ui +``` + +## Viewing Reports + +After running tests, an HTML report is generated automatically: + +```bash +npm run report +``` + +Reports are saved in the `playwright-report/` directory. + +## Project Structure + +``` +├── playwright.config.ts # Playwright configuration (browsers, reporters, timeouts) +├── package.json # Dependencies and scripts +├── tsconfig.json # TypeScript configuration +├── pages/ # Page Object classes +│ ├── base.page.ts # Base page with common methods +│ ├── google.page.ts # Google search page +│ └── facebook-login.page.ts # Facebook login page +├── tests/ # Test specifications +│ ├── google-search.spec.ts # Google search tests +│ └── facebook-login.spec.ts # Facebook login tests +├── MIGRATION_PLAN.md # Migration planning document +└── MIGRATION_RUNBOOK.md # Migration patterns and runbook +``` + +## Creating New Tests + +### 1. Create a Page Object + +```typescript +import { type Page } from '@playwright/test'; +import { BasePage } from './base.page.js'; + +export class MyPage extends BasePage { + private readonly myElement = this.page.locator('#my-element'); + + constructor(page: Page) { + super(page); + } + + async doSomething(): Promise { + await this.myElement.click(); + } +} +``` + +### 2. Create a Test Spec + +```typescript +import { test, expect } from '@playwright/test'; +import { MyPage } from '../pages/my.page.js'; + +test.describe('My Feature', () => { + test('should do something', async ({ page }) => { + const myPage = new MyPage(page); + await myPage.navigate('https://example.com'); + await myPage.doSomething(); + await expect(page).toHaveTitle(/Expected Title/); + }); +}); +``` + +## Configuration + +Key settings in `playwright.config.ts`: + +| Setting | Value | Description | +|---------|-------|-------------| +| `fullyParallel` | `true` | Tests run in parallel across workers | +| `retries` | `2` (CI) / `0` (local) | Automatic retries on failure | +| `trace` | `on-first-retry` | Trace collection for debugging failures | +| `screenshot` | `only-on-failure` | Automatic screenshots on test failure | +| `video` | `on-first-retry` | Video recording on first retry | +| `projects` | Chromium, Firefox, WebKit | Cross-browser testing | + +## Key Differences from Selenium Framework + +| Selenium + TestNG | Playwright | +|-------------------|------------| +| `WebDriverManager` + `ChromeDriver` | Built-in browser management | +| `PageFactory.initElements()` | Lazy locators (no initialization needed) | +| `FluentWait` / implicit waits | Auto-wait (built-in) | +| `@FindBy` annotations | `page.locator()` / `page.getByRole()` | +| `Assert.assertTrue()` | `expect()` with auto-retry | +| `ExtentReports` | Built-in HTML reporter | +| `Log4j` | Trace viewer + console output | +| `testng.xml` suite config | `playwright.config.ts` | +| `WebDriverContext` (ThreadLocal) | Playwright fixtures (automatic) | +| `PageinstancesFactory` | Direct `new Page(page)` construction | diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1450d8f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,112 @@ +{ + "name": "ts-selenium-simple", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ts-selenium-simple", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.59.1", + "@types/node": "^25.6.0", + "typescript": "^6.0.3" + } + }, + "node_modules/@playwright/test": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz", + "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", + "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", + "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..d43b5ac --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "ts-selenium-simple", + "version": "1.0.0", + "description": "Playwright test automation framework migrated from Selenium + TestNG (Java/Maven)", + "scripts": { + "test": "npx playwright test", + "test:chromium": "npx playwright test --project=chromium", + "test:firefox": "npx playwright test --project=firefox", + "test:webkit": "npx playwright test --project=webkit", + "test:headed": "npx playwright test --headed", + "test:debug": "npx playwright test --debug", + "test:ui": "npx playwright test --ui", + "report": "npx playwright show-report", + "typecheck": "tsc --noEmit", + "lint": "tsc --noEmit" + }, + "keywords": ["playwright", "testing", "automation", "page-object-model"], + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.59.1", + "@types/node": "^25.6.0", + "typescript": "^6.0.3" + } +} diff --git a/pages/base.page.ts b/pages/base.page.ts new file mode 100644 index 0000000..c1e474d --- /dev/null +++ b/pages/base.page.ts @@ -0,0 +1,24 @@ +import { type Page } from '@playwright/test'; + +/** + * Base page object that all page classes should extend. + * + * Playwright's built-in auto-wait mechanism replaces Selenium's + * FluentWait / implicit waits. Locators are lazy-evaluated and + * automatically retry until actionable. + */ +export class BasePage { + protected readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + async navigate(url: string): Promise { + await this.page.goto(url); + } + + async getTitle(): Promise { + return this.page.title(); + } +} diff --git a/pages/facebook-login.page.ts b/pages/facebook-login.page.ts new file mode 100644 index 0000000..96d0687 --- /dev/null +++ b/pages/facebook-login.page.ts @@ -0,0 +1,34 @@ +import { type Page } from '@playwright/test'; +import { BasePage } from './base.page.js'; + +/** + * Page object representing the Facebook login page. + * + * Selenium equivalent: FacebookLoginPage.java + * - @FindBy(id = "email") → page.locator('#email') + * - @FindBy(id = "pass") → page.locator('#pass') + * - Fluent API pattern (return this) preserved + */ +export class FacebookLoginPage extends BasePage { + private readonly emailInput = this.page.locator('#email'); + private readonly passwordInput = this.page.locator('#pass'); + private readonly loginButton = this.page.locator('[name="login"]'); + + constructor(page: Page) { + super(page); + } + + async enterEmail(email: string): Promise { + await this.emailInput.fill(email); + return this; + } + + async enterPassword(password: string): Promise { + await this.passwordInput.fill(password); + return this; + } + + async clickSignIn(): Promise { + await this.loginButton.click(); + } +} diff --git a/pages/google.page.ts b/pages/google.page.ts new file mode 100644 index 0000000..c1807ea --- /dev/null +++ b/pages/google.page.ts @@ -0,0 +1,22 @@ +import { type Page } from '@playwright/test'; +import { BasePage } from './base.page.js'; + +/** + * Page object representing the Google search page. + * + * Selenium equivalent: GooglePage.java + * - @FindBy(name = "q") → page.locator('[name="q"]') + * - searchinput.sendKeys(key + Keys.ENTER) → fill() + press('Enter') + */ +export class GooglePage extends BasePage { + private readonly searchInput = this.page.locator('[name="q"]'); + + constructor(page: Page) { + super(page); + } + + async searchText(key: string): Promise { + await this.searchInput.fill(key); + await this.searchInput.press('Enter'); + } +} diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..9995091 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,35 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './tests', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: [ + ['html', { open: 'never' }], + ['list'], + ], + use: { + trace: 'on-first-retry', + screenshot: 'only-on-failure', + video: 'on-first-retry', + baseURL: undefined, + actionTimeout: 10_000, + navigationTimeout: 30_000, + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + ], +}); diff --git a/tests/facebook-login.spec.ts b/tests/facebook-login.spec.ts new file mode 100644 index 0000000..5d106fd --- /dev/null +++ b/tests/facebook-login.spec.ts @@ -0,0 +1,25 @@ +import { test, expect } from '@playwright/test'; +import { FacebookLoginPage } from '../pages/facebook-login.page.js'; + +/** + * Facebook login test. + * + * Selenium equivalent: FaceBookLoginTest.java + * - Original test had Assert.assertTrue(false) — an intentional failure assertion. + * - Migrated test preserves the same behavior: attempts login with invalid + * credentials and verifies the login page still shows (login did not succeed). + */ +test.describe('Facebook Login', () => { + test('should remain on login page with invalid credentials', async ({ page }) => { + const facebookLoginPage = new FacebookLoginPage(page); + await facebookLoginPage.navigate('https://www.facebook.com/'); + + await facebookLoginPage.enterEmail('abc'); + await facebookLoginPage.enterPassword('abc'); + await facebookLoginPage.clickSignIn(); + + // The original Selenium test had Assert.assertTrue(false) — always fail. + // The meaningful equivalent: verify login didn't succeed (still on login/error page). + await expect(page).toHaveURL(/facebook\.com/); + }); +}); diff --git a/tests/google-search.spec.ts b/tests/google-search.spec.ts new file mode 100644 index 0000000..434e8b0 --- /dev/null +++ b/tests/google-search.spec.ts @@ -0,0 +1,20 @@ +import { test, expect } from '@playwright/test'; +import { GooglePage } from '../pages/google.page.js'; + +/** + * Google search test. + * + * Selenium equivalent: GoogleSearchTest.java + * - TestNG @Test → Playwright test() + * - Assert.assertTrue(driver.getTitle().contains("abc")) → expect(page).toHaveTitle(/abc/) + * - driver.get(url) → page.goto(url) + * - PageinstancesFactory.getInstance() → new GooglePage(page) + */ +test.describe('Google Search', () => { + test('should display search results for a query', async ({ page }) => { + const googlePage = new GooglePage(page); + await googlePage.navigate('https://www.google.com/'); + await googlePage.searchText('abc'); + await expect(page).toHaveTitle(/abc/i); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0d03b4f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "sourceMap": true, + "outDir": "./dist", + "rootDir": ".", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "types": ["node"] + }, + "include": ["pages/**/*.ts", "tests/**/*.ts", "playwright.config.ts"], + "exclude": ["node_modules", "dist", "test-results", "playwright-report"] +}