Real Projects
Hands-on UI automation projects built with Playwright, TypeScript, Selenium, and Java, focused on real-world test scenarios and maintainable automation.
Hasan Akdogan · QA Automation Engineer · 1-page PDF
Loading interactive demos…
Quick access to demos, labs, reports, and contact links.
Hasan Akdogan
QA Automation Engineer
Playwright • CI/CD • API Testing
QA Automation Engineer focused on scalable test automation, quality engineering, and reliable delivery.
Test Automation Quality Assurance ISTQB® FL Certified
Open to Remote Opportunities
Test automation, pipelines & release-quality signals
Hands-on experience in UI test automation using Playwright (TypeScript) and Selenium (Java), complemented by API testing and database validation.
Stack
Hands-on UI automation projects built with Playwright, TypeScript, Selenium, and Java, focused on real-world test scenarios and maintainable automation.
Experience designing and maintaining UI automation frameworks, reusable test components, and Smoke & Regression suites integrated with Jenkins CI/CD.
Hands-on experience in functional, regression, exploratory, and manual testing, supported by DevTools, API responses, and application logs for defect investigation.
My practice platform for manual and automation testing — real UI, forms, flows, APIs and bugs to find.
A free web app I designed and built for practising QA. Every page is made to be tested: try it by hand first, then automate it with Playwright or Selenium.
What you can practise
Free · no install · guest session available — opens qapractice.hakdogan.com in a new tab.
Simulated headed run — the lab opens in your browser when you use the CTA.
How I test a feature from start to finish — one login story through every step: analysis, test case, manual test, automation, report, and a real bug found, reported and retested.
| User Story | As a user, I should log in successfully and leave the session safely. |
|---|---|
| Acceptance Criteria | User can log in with valid credentials, access the Products page, log out successfully, and return to the login form. |
| Requirement Analysis |
Questions for the PO / extra scenarios:
|
| Test Case ID | TC-LOGIN-001 |
| Test Case Description | Verify successful login with valid credentials and complete Sauce Demo login/logout flow. |
| Pre-Condition | Application is accessible and demo environment is available. |
| Test Steps and Description |
|
| Test Data |
URL:
https://www.saucedemo.com
User: standard_user
Pass: secret_sauce
|
| Expected Result | User logs in successfully, logs out successfully, and the login form is displayed again. |
| Execution | Manual first (Chrome) → then automated: critical, stable and repeated in every regression. |
| Actual Result | Actual behavior matches expected result. Test completed successfully. |
| Comments / evidence | Demo execution output confirms successful flow. |
| Pass / Fail | PASS |
Feature: User Authentication
Scenario: Successful login and logout
Given the login page is displayed
When I log in with valid credentials
Then the inventory page should be visible
When I click Logout
Then the login page should be displayed again
import { test, expect } from '@playwright/test'; import { LoginPage } from './pages/LoginPage'; import { InventoryPage } from './pages/InventoryPage'; test.describe('SauceDemo — auth', () => { let loginPage: LoginPage; let inventoryPage: InventoryPage; test.beforeEach(async ({ page }) => { loginPage = new LoginPage(page); inventoryPage = new InventoryPage(page); await loginPage.goto(); }); test('TC-LOGIN-001: login → inventory → logout', async () => { await loginPage.login('standard_user', 'secret_sauce'); await inventoryPage.expectLoaded(); await inventoryPage.logout(); await expect(loginPage.loginButton).toBeVisible(); }); });
// pages/LoginPage.ts import { type Page, type Locator, expect } from '@playwright/test'; export class LoginPage { readonly username: Locator; readonly password: Locator; readonly loginButton: Locator; readonly errorMessage: Locator; constructor(private readonly page: Page) { this.username = page.locator('[data-test="username"]'); this.password = page.locator('[data-test="password"]'); this.loginButton = page.locator('[data-test="login-button"]'); this.errorMessage = page.locator('[data-test="error"]'); } async goto() { await this.page.goto('/'); // baseURL in playwright.config.ts await expect(this.loginButton).toBeVisible(); } async login(user: string, pass: string) { await this.username.fill(user); await this.password.fill(pass); await this.loginButton.click(); } } // pages/InventoryPage.ts import { type Page, type Locator, expect } from '@playwright/test'; export class InventoryPage { readonly productList: Locator; readonly menuButton: Locator; readonly logoutLink: Locator; constructor(private readonly page: Page) { this.productList = page.locator('.inventory_list'); this.menuButton = page.getByRole('button', { name: 'Open Menu' }); this.logoutLink = page.getByRole('link', { name: 'Logout' }); } async expectLoaded() { await expect(this.page).toHaveURL(/inventory/); await expect(this.productList).toBeVisible(); } async logout() { await this.menuButton.click(); await this.logoutLink.click(); } }
Pick a build and click Run test. Build v1.0.1 has a logout bug, so the test fails. Report the bug, then retest on the fixed build v1.0.2.
Execution summary
Test failed — bug found
After clicking Logout the user stays on /inventory.html. I checked it manually too: it is a product bug, not a flaky test.
Retest passed — bug can be closed
Same test on build v1.0.2 passes. The test stays in the regression suite so this bug cannot come back unnoticed.
There are no environment variables (simulation)
| Test name | Status | Duration |
|---|---|---|
— |
— | — |
On a real run, Playwright saves PNGs per step or on failure. In this demo, assert thumbnails (login, inventory, logout) load only after the run finishes; until then, the slots stay empty.
On a real run, set video: 'on' or
'retain-on-failure' in
playwright.config — output often lands
in test-results/. In this demo, a screen recording
(video evidence) is attached only after the run
completes.
Demo file: videos/pw-run-demo-evidence.mp4 — optimized screen capture of the
login → inventory → logout run.
This panel is a front-end simulation. In production, wire the same layout to your reporter output or Allure JSON — the workflow matches how QA engineers triage runs after npx playwright test.
This interactive lab is loading. If this message stays, please refresh the page.
This interactive lab is loading. If this message stays, please refresh the page.
This interactive lab is loading. If this message stays, please refresh the page.
Want to test real QA scenarios? Open QA Practice — register, login, API mock, dashboard, and Playwright-ready flows.
Open QA Practice ↗Interactive demos that showcase real tools, coding workflows, automation logic, and technical execution.
In my workBuilt a Playwright + TypeScript framework with POM from scratch in my last role.
test('login flow', async ({ page }) => {
await page.goto('/login');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page).toHaveURL(/app/);
});
In my workThe language of my Playwright framework.
type User = { id: string };
const greet = (u: User) =>
`hi, ${u.id}`;
In my workUsed in a previous role as part of the test workflow.
In my workSQL for database validation in my tests.
SELECT id, email, created_at
FROM users
WHERE active = true
LIMIT 3;
| id | created_at | |
|---|---|---|
| 1 | qa@x.dev | 2025-11-01 |
| 2 | lead@x.dev | 2025-10-20 |
In my workManual API testing; API automation with RestAssured.
{
"status": "ok",
"version": "1.0"
}
In my workVersion control for my projects on GitHub.
In my workPrevious role: Selenium, JUnit, RestAssured and JDBC.
public class Main {
public static void main(String[] a) {
System.out.println("Hello");
}
}
In my workPrevious role: UI automation with Selenium WebDriver + Java.
driver.findElement(By.id("login"))
.click();
assert driver.getCurrentUrl()...
In my workPrevious role: BDD scenarios with Cucumber + Java.
Feature: Login
Scenario: valid user
Given the login page
When I submit credentials
Then I see the dashboard
In my workBug tracking; test management with Xray.
const run = () => {
document.querySelector('#out')
.textContent = 'ok';
};
run();
Selected resources for QA careers, interview prep, real work expectations, and growth in testing.
Learn core QA, automation, and software topics — plus quick Cheat Sheets for fast revision.
The six phases of the software development life cycle: requirements, design, development, testing, release and maintenance.
The six phases of the software development life cycle: requirements, design, development, testing, release and maintenance.
How a two-week Scrum sprint works and what happens in each of the five Scrum events.
How a two-week Scrum sprint works and what happens in each of the five Scrum events.
In our project, I have been working with the Agile Scrum methodology.
Our sprint cycle is 2 weeks and starts every other week on Monday.
In our team, we have a Product Owner, Scrum Master, 5 Developers, and 3 Testers.
We have 5 meetings in this sprint time period.
A worked example: testing a vending machine, from requirements and acceptance criteria to negative cases.
A worked example: testing a vending machine, from requirements and acceptance criteria to negative cases.
When testing a vending machine, I first check all requirements and acceptance
criteria as a tester.
Users can purchase the product with valid payment. For example cash,
debit/credit card, apple pay, etc.
Creating effective scenarios with positive, negative, boundary and edge case coverage.
Creating effective scenarios with positive, negative, boundary and edge case coverage.
Test case design is the process of creating effective scenarios to
validate application behavior.
Strong test design helps QA engineers find bugs early and improve
coverage.
Key Concepts
1. Positive Testing
Verify expected behavior with valid inputs.
2. Negative Testing
Verify system handles invalid inputs correctly.
3. Boundary Value Analysis
Test limits such as min/max values.
4. Equivalence Partitioning
Divide inputs into valid and invalid groups.
5. Edge Cases
Unexpected or uncommon user behavior.
6. Risk-Based Testing
Prioritize critical flows first.
Example: Login page
valid login
wrong password
empty fields
max length inputs
SQL injection attempt
Conclusion
Good automation starts with good test case design.
The 8 Selenium locators (id, name, className, tagName, linkText, partialLinkText, cssSelector, XPath) and when to use each.
The 8 Selenium locators (id, name, className, tagName, linkText, partialLinkText, cssSelector, XPath) and when to use each.
A design pattern that keeps page elements and actions in page classes, so tests stay readable, maintainable and reusable.
A design pattern that keeps page elements and actions in page classes, so tests stay readable, maintainable and reusable.
Why elements load at different times, and how Thread.sleep, implicit, explicit and fluent waits handle it.
Why elements load at different times, and how Thread.sleep, implicit, explicit and fluent waits handle it.
Thread.Sleep method comes from Java and is not a good practice to
use because it will wait for regardless of the given time in
milliseconds.
ImplicitlyWait comes from Selenium library. It is ONLY triggered
when findElement (@FindBy) method can NOT find a web element with
given locator. It will wait until the web element is found or timer
runs out. We use it once, and it will be applied to the rest of our
driver session life. When the given time expires
NoSuchElementException is thrown by findElement method.
ExplicitWait comes from Selenium library. It can wait for expected
conditions to happen on the page.
It is mainly used when we want to click or act on an object when it
is visible. We can use a lot of Expected Conditions can be used in
Explicit Wait. Some of them are:
elementToBeClickable()
elementToBeSelected()
visibilityOf()
FluentWait is quite similar to explicit Wait and it defines how
frequently WebDriver will check if the condition appears before
throwing the ElementNotVisibleException
A modern framework with auto-waiting, cross-browser support and strong debugging tools for reliable tests.
A modern framework with auto-waiting, cross-browser support and strong debugging tools for reliable tests.
Playwright is a modern end-to-end test automation framework used for
testing web applications across Chromium, Firefox, and WebKit.
It is especially useful for QA Automation Engineers because it provides
reliable element handling, fast execution, and strong debugging tools.
Key Advantages
1. Cross-Browser Testing
Playwright supports Chromium, Firefox, and WebKit. This allows the same
test to run across multiple browser engines.
2. Auto-Waiting
Playwright automatically waits for elements to become visible, enabled,
and ready before interacting with them. This helps reduce flaky tests.
3. Stable Selectors
Playwright supports multiple selector strategies such as:
getByRole
getByText
getByLabel
getByTestId
locator
Using stable selectors improves test reliability.
4. Powerful Assertions
Playwright includes built-in assertions such as:
toBeVisible()
toHaveText()
toHaveURL()
toBeEnabled()
5. Debugging Support
Playwright provides useful debugging tools:
Trace Viewer
Screenshots
Video recording
HTML report
Example Playwright Test
import { test, expect } from '@playwright/test';
test('login test', async ({ page }) => {
await page.goto('https://example.com');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page).toHaveURL(/dashboard/);
});
Conclusion
Playwright is a strong choice for modern QA automation because it helps
create fast, readable, and reliable tests.
Hiding data with private fields and exposing it safely through public getters and setters.
Hiding data with private fields and exposing it safely through public getters and setters.
Hiding implementation details and showing only what is needed: abstract classes, abstract methods and interfaces.
Hiding implementation details and showing only what is needed: abstract classes, abstract methods and interfaces.
One reference, many forms: method overriding and overloading, upcasting and downcasting.
One reference, many forms: method overriding and overloading, upcasting and downcasting.
Polymorphism is one of the Object Oriented Programming principles. Polymorphism
means many forms.
Occurs when the reference type is a parent class or interface and the object
type is a child.
Everyday Git commands (clone, status, add, commit, branch, checkout, merge, pull, push) and resolving merge conflicts.
Everyday Git commands (clone, status, add, commit, branch, checkout, merge, pull, push) and resolving merge conflicts.
There are many different ways to use Git. Git supports many command-line tools
and graphical user interfaces. The Git command line is the only place where you
can run all the Git commands.
The following set of commands will help you understand how to use Git via the
command line.
Open to remote opportunities, collaborations, freelance projects, and professional connections.
Jump to any section, open a resource, or follow my work.
QA Automation Engineer