Toolmingo
Guides18 min read

QA Engineer Roadmap 2025 (Step-by-Step Guide)

The complete QA engineer roadmap for 2025 — manual testing, automation, Selenium, Playwright, API testing, performance testing, CI/CD, and certifications. Know exactly what to learn and in what order.

Quality Assurance engineering is one of the most stable and consistently in-demand roles in tech. Every software team needs QA, yet it's far less crowded than software engineering. This roadmap shows you exactly what to learn, in what order — from zero manual tester to automation engineer with a full CI/CD pipeline, in 9–12 months.

At a glance

Phase Topics Time estimate
1 Testing fundamentals and SDLC 3–4 weeks
2 Manual testing skills 4–5 weeks
3 SQL and databases for testers 2–3 weeks
4 API testing (Postman, REST) 3–4 weeks
5 Programming basics (Python or JavaScript) 6–8 weeks
6 Selenium WebDriver and automation frameworks 6–8 weeks
7 Modern automation: Playwright, Cypress 4–6 weeks
8 Performance and load testing 3–4 weeks
9 CI/CD integration and DevOps basics 3–4 weeks
10 Mobile testing 2–3 weeks
11 Certifications and portfolio 3–4 weeks
Total to first automation role ~9–12 months

Phase 1 — Testing fundamentals and SDLC

Before you write a single test, you need to understand what testing is for and where it fits in the software development lifecycle.

SDLC models

Model Description When used
Waterfall Sequential phases, testing at end Legacy, regulated industries
Agile (Scrum/Kanban) Iterative sprints, continuous testing Most modern teams
V-Model Testing mapped to each dev phase Safety-critical systems
DevOps / CI-CD Automated tests run every commit High-release-velocity teams

Test types hierarchy

E2E / UI tests         ← few, slow, expensive (UI layer)
        ↑
Integration tests      ← moderate, test component interaction
        ↑
Unit tests             ← many, fast, cheap (function level)

The Testing Pyramid principle: most tests should be unit tests. Heavy reliance on E2E tests is the most common anti-pattern.

Core vocabulary every QA must know

Term Definition
Test case Step-by-step instructions to verify a specific behaviour
Test suite Collection of related test cases
Bug / defect Deviation from expected behaviour
Regression Bug introduced by a change that worked before
Smoke test Quick check that core functionality works after a build
Sanity test Narrow check after a bug fix or minor change
Exploratory testing Unscripted, creative testing without predefined cases
UAT User Acceptance Testing — final check before release
Severity vs priority Severity = impact on user; Priority = urgency of fix

Bug reporting best practice

A good bug report contains:

  1. Title — short, specific ("Login button unresponsive on Safari 17")
  2. Environment — OS, browser, app version, device
  3. Preconditions — account type, test data needed
  4. Steps to reproduce — numbered, exact
  5. Expected result — what should happen
  6. Actual result — what actually happens
  7. Severity/Priority — your assessment
  8. Attachments — screenshots, video, logs

Phase 2 — Manual testing skills

Automation doesn't replace manual testing — it frees your time for the testing only humans can do.

Test design techniques

Technique What it tests Example
Equivalence partitioning Valid + invalid input classes Age field: <0, 0–120, >120
Boundary value analysis Edges of valid range 0, 1, 119, 120, 121
Decision table Combinations of conditions/actions Login with 2FA: 4 combinations
State transition Object moves between states Order: Created→Paid→Shipped→Delivered
Pairwise / combinatorial All pairs of input combinations Reduces 1000s of cases to ~30

Test case writing template

ID: TC-LOGIN-001
Title: Valid credentials login
Preconditions: User account exists, email=test@example.com, pass=Test1234!
Steps:
  1. Navigate to /login
  2. Enter email: test@example.com
  3. Enter password: Test1234!
  4. Click "Log in"
Expected: User redirected to /dashboard, username visible in header
Priority: High

What to test beyond the happy path

  • Negative tests — invalid inputs, wrong passwords, missing required fields
  • Edge cases — empty strings, null, very long inputs, special characters (emoji, SQL)
  • Boundary conditions — min/max values, 0, -1, empty arrays
  • Concurrency — two users editing the same record simultaneously
  • Security basics — XSS in input fields, path traversal, IDOR (object reference)
  • Accessibility — keyboard navigation, screen reader labels, colour contrast

Phase 3 — SQL and databases for testers

QA engineers constantly need to verify data in the database — don't rely on the UI alone.

Essential SQL for QA

-- Verify user was created
SELECT id, email, created_at FROM users WHERE email = 'test@example.com';

-- Check order total matches UI
SELECT SUM(price * quantity) as total
FROM order_items WHERE order_id = 12345;

-- Find orphaned records (data integrity check)
SELECT o.id FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE u.id IS NULL;

-- Count records for pagination validation
SELECT COUNT(*) FROM products WHERE category = 'electronics';

-- Verify soft-delete logic
SELECT id, deleted_at FROM posts WHERE id = 99;

Joins testers must know

Join type Returns
INNER JOIN Only matching rows in both tables
LEFT JOIN All rows from left + matches from right
RIGHT JOIN All rows from right + matches from left
FULL OUTER JOIN All rows from both, nulls for mismatches

Test data management

  • Use dedicated test databases — never test against production
  • Reset test data between test runs for repeatability
  • Use factories/fixtures to generate consistent test data
  • Be careful with PII — anonymize production dumps

Tools: Faker (Python/JS) generates realistic fake data; DB Fiddle for quick SQL practice.


Phase 4 — API testing

Most modern apps are built on APIs. API testing is faster, more reliable, and finds bugs that UI tests miss.

REST API basics for testers

Method Purpose Success code
GET Read resource 200
POST Create resource 201
PUT Replace resource 200
PATCH Partial update 200
DELETE Remove resource 204

HTTP status codes you must know

Code Meaning What to test
200 OK Body matches spec
201 Created Location header, body has new ID
400 Bad request Error message is descriptive
401 Unauthorized No/invalid token
403 Forbidden Valid token, wrong permissions
404 Not found ID doesn't exist
409 Conflict Duplicate resource
422 Unprocessable Validation errors in body
500 Server error Should never leak stack traces

Postman workflow

Collection: User API
├── Folder: Auth
│   ├── POST /auth/register    → save {{token}} to env
│   └── POST /auth/login       → save {{token}} to env
├── Folder: Users
│   ├── GET  /users/me         → verify fields
│   ├── PUT  /users/me         → update + verify
│   └── DELETE /users/me       → 204 + verify gone
└── Folder: Edge cases
    ├── POST /auth/login (wrong password)  → 401
    ├── GET  /users/99999 (missing)        → 404
    └── POST /users (duplicate email)      → 409

Pre-request scripts — set up auth tokens, timestamps, unique emails:

pm.environment.set("unique_email", `test_${Date.now()}@example.com`);

Test scripts — assert responses in Postman:

pm.test("Status is 200", () => pm.response.to.have.status(200));
pm.test("Has user id", () => {
  const body = pm.response.json();
  pm.expect(body.id).to.be.a("number");
  pm.expect(body.email).to.equal(pm.environment.get("test_email"));
});
pm.environment.set("user_id", pm.response.json().id);

Newman — run Postman in CI

# Install
npm install -g newman

# Run collection
newman run my-collection.json -e staging.json --reporters cli,junit --reporter-junit-export results.xml

Phase 5 — Programming basics (Python or JavaScript)

You need one language well enough to write automation. Python is the most popular QA choice; JavaScript if you're testing a JS-heavy app.

Python for QA — the essentials

# String manipulation
url = f"https://api.example.com/users/{user_id}"
assert "error" not in response.text.lower()

# List comprehension
failed_tests = [t for t in results if t["status"] == "failed"]

# File handling — read test data CSV
import csv
with open("test_data.csv") as f:
    users = list(csv.DictReader(f))

# HTTP requests — manual API testing
import requests
response = requests.post(
    "https://api.example.com/auth/login",
    json={"email": "test@example.com", "password": "Test1234!"}
)
assert response.status_code == 200
token = response.json()["token"]

# Environment variables — keep secrets out of code
import os
base_url = os.getenv("API_BASE_URL", "https://staging.example.com")

JavaScript for QA (Node.js)

// Fetch API
const response = await fetch(`${BASE_URL}/users/${userId}`, {
  headers: { Authorization: `Bearer ${token}` }
});
const user = await response.json();
console.assert(user.email === expectedEmail);

// Array methods
const failedCases = results.filter(r => r.status !== "passed");
const emails = users.map(u => u.email);

// Async/await pattern common in Playwright/Cypress
async function login(page, email, password) {
  await page.fill('[name="email"]', email);
  await page.fill('[name="password"]', password);
  await page.click('[type="submit"]');
  await page.waitForURL("**/dashboard");
}

Phase 6 — Selenium WebDriver and automation frameworks

Selenium is the foundation of browser automation. Even if you move to Playwright later, understanding Selenium makes you stronger.

Selenium with Python (pytest)

# conftest.py — shared fixtures
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

@pytest.fixture(scope="session")
def driver():
    options = Options()
    options.add_argument("--headless")
    options.add_argument("--no-sandbox")
    driver = webdriver.Chrome(options=options)
    driver.implicitly_wait(10)
    yield driver
    driver.quit()

# test_login.py
from selenium.webdriver.common.by import By

def test_valid_login(driver):
    driver.get("https://example.com/login")
    driver.find_element(By.NAME, "email").send_keys("test@example.com")
    driver.find_element(By.NAME, "password").send_keys("Test1234!")
    driver.find_element(By.CSS_SELECTOR, "[type='submit']").click()
    assert "/dashboard" in driver.current_url

def test_invalid_login_shows_error(driver):
    driver.get("https://example.com/login")
    driver.find_element(By.NAME, "email").send_keys("wrong@example.com")
    driver.find_element(By.NAME, "password").send_keys("wrongpass")
    driver.find_element(By.CSS_SELECTOR, "[type='submit']").click()
    error = driver.find_element(By.CLASS_NAME, "error-message")
    assert "Invalid credentials" in error.text

Locator strategy priority

Priority Locator Example Why
1st data-testid / data-cy [data-testid="submit-btn"] Stable, purpose-built
2nd id #submit-button Unique, fast
3rd name [name="email"] Semantic HTML
4th CSS selector .btn-primary Readable, flexible
5th XPath //button[text()='Submit'] Last resort
Avoid XPath with index //div[3]/span[1] Brittle, breaks on layout change

Page Object Model (POM)

The most important pattern in test automation — keeps tests readable and maintainable.

# pages/login_page.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class LoginPage:
    URL = "/login"

    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)

    def open(self, base_url):
        self.driver.get(f"{base_url}{self.URL}")
        return self

    def enter_email(self, email):
        self.driver.find_element(By.NAME, "email").send_keys(email)
        return self

    def enter_password(self, password):
        self.driver.find_element(By.NAME, "password").send_keys(password)
        return self

    def submit(self):
        self.driver.find_element(By.CSS_SELECTOR, "[type='submit']").click()
        return self

    def get_error(self):
        return self.wait.until(
            EC.visibility_of_element_located((By.CLASS_NAME, "error-message"))
        ).text

# tests/test_login.py
def test_valid_login(driver, base_url):
    page = LoginPage(driver).open(base_url)
    page.enter_email("test@example.com").enter_password("Test1234!").submit()
    assert "/dashboard" in driver.current_url

Phase 7 — Modern automation: Playwright and Cypress

Playwright and Cypress have largely replaced Selenium for new projects. They're faster, more reliable, and have better developer experience.

Playwright (Python or TypeScript)

// tests/login.spec.ts
import { test, expect } from "@playwright/test";

test.describe("Login", () => {
  test("valid credentials → redirect to dashboard", async ({ page }) => {
    await page.goto("/login");
    await page.fill('[name="email"]', "test@example.com");
    await page.fill('[name="password"]', "Test1234!");
    await page.click('[type="submit"]');
    await expect(page).toHaveURL(/dashboard/);
  });

  test("invalid password → error message", async ({ page }) => {
    await page.goto("/login");
    await page.fill('[name="email"]', "test@example.com");
    await page.fill('[name="password"]', "wrongpassword");
    await page.click('[type="submit"]');
    await expect(page.locator(".error-message")).toContainText("Invalid");
  });

  test("API-seeded user login", async ({ page, request }) => {
    // Create user via API — faster than UI setup
    const res = await request.post("/api/users", {
      data: { email: `test_${Date.now()}@example.com`, password: "Test1234!" }
    });
    const { email } = await res.json();

    await page.goto("/login");
    await page.fill('[name="email"]', email);
    await page.fill('[name="password"]', "Test1234!");
    await page.click('[type="submit"]');
    await expect(page).toHaveURL(/dashboard/);
  });
});

playwright.config.ts

import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
  testDir: "./tests",
  timeout: 30_000,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 4 : undefined,
  reporter: [["html"], ["junit", { outputFile: "results.xml" }]],
  use: {
    baseURL: process.env.BASE_URL ?? "http://localhost:3000",
    trace: "on-first-retry",
    screenshot: "only-on-failure",
    video: "retain-on-failure",
  },
  projects: [
    { name: "chromium", use: { ...devices["Desktop Chrome"] } },
    { name: "firefox", use: { ...devices["Desktop Firefox"] } },
    { name: "webkit", use: { ...devices["Desktop Safari"] } },
    { name: "mobile-chrome", use: { ...devices["Pixel 5"] } },
  ],
});

Playwright vs Cypress at a glance

Feature Playwright Cypress
Multi-browser Chrome, Firefox, Safari Chrome, Edge, Firefox (no Safari)
Multi-tab Yes Limited
Language support JS/TS, Python, Java, C# JS/TS only
Component testing Yes (experimental) Yes (mature)
Network interception Yes Yes
Parallel execution Built-in, fast Cypress Cloud (paid)
Learning curve Moderate Easier
Best for Cross-browser, multi-lang teams JS teams, great DX

Phase 8 — Performance and load testing

Find bottlenecks before users do.

k6 — load testing as code

// load-test.js
import http from "k6/http";
import { check, sleep } from "k6";
import { Rate } from "k6/metrics";

const errorRate = new Rate("errors");

export const options = {
  stages: [
    { duration: "30s", target: 20 },   // ramp up to 20 users
    { duration: "1m", target: 20 },    // stay at 20 users
    { duration: "30s", target: 100 },  // spike to 100 users
    { duration: "30s", target: 0 },    // ramp down
  ],
  thresholds: {
    http_req_duration: ["p(95)<500"],  // 95% of requests < 500ms
    errors: ["rate<0.01"],             // error rate < 1%
  },
};

export default function () {
  const res = http.get("https://staging.example.com/api/products");
  const ok = check(res, {
    "status is 200": r => r.status === 200,
    "response time < 500ms": r => r.timings.duration < 500,
  });
  errorRate.add(!ok);
  sleep(1);
}
k6 run load-test.js
k6 run --out json=results.json load-test.js

Key performance metrics

Metric Description Good target
Response time p50 Median response time <200ms
Response time p95 95th percentile <500ms
Response time p99 99th percentile <1000ms
Throughput (RPS) Requests per second Depends on SLA
Error rate % requests that failed <0.1%
TTFB Time to first byte <100ms
Apdex User satisfaction score 0–1 >0.9

Types of performance tests

Test type Goal
Load test Verify normal expected load
Stress test Find breaking point beyond expected load
Spike test Sudden traffic burst (flash sale, news event)
Soak test Sustained load over hours — finds memory leaks
Volume test Large datasets — pagination, DB query performance

Phase 9 — CI/CD integration

Automated tests only deliver value when they run automatically on every change.

GitHub Actions — full QA pipeline

# .github/workflows/qa.yml
name: QA Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -r requirements.txt
      - run: pytest tests/unit/ --junitxml=unit-results.xml --cov=src --cov-report=xml
      - uses: actions/upload-artifact@v4
        with:
          name: unit-test-results
          path: unit-results.xml

  api-tests:
    runs-on: ubuntu-latest
    needs: unit-tests
    steps:
      - uses: actions/checkout@v4
      - run: pip install requests pytest
      - run: pytest tests/api/ --junitxml=api-results.xml
        env:
          API_BASE_URL: ${{ secrets.STAGING_URL }}
          API_TOKEN: ${{ secrets.STAGING_TOKEN }}

  e2e-tests:
    runs-on: ubuntu-latest
    needs: api-tests
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npx playwright test --reporter=html
        env:
          BASE_URL: ${{ secrets.STAGING_URL }}
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/

  performance-test:
    runs-on: ubuntu-latest
    needs: e2e-tests
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - run: |
          curl https://github.com/grafana/k6/releases/download/v0.52.0/k6-v0.52.0-linux-amd64.tar.gz -L | tar xvz
          sudo mv k6-*/k6 /usr/local/bin/
      - run: k6 run tests/performance/load-test.js

Test pyramid in CI — timing strategy

Stage Tests When Target time
Pre-commit (local) Unit tests Every save <10s
PR check Unit + integration Every PR <5 min
Merge to develop Full E2E (smoke) Every merge <15 min
Nightly Full E2E + regression Every night <1 hour
Release gate E2E + performance Before release <2 hours

Phase 10 — Mobile testing

Mobile apps have unique failure modes: different screen sizes, OS versions, touch gestures, interruptions.

Appium basics

# Mobile web on Android Chrome
from appium import webdriver
from appium.options import UiAutomator2Options

options = UiAutomator2Options()
options.platform_name = "Android"
options.device_name = "emulator-5554"
options.browser_name = "Chrome"

driver = webdriver.Remote("http://localhost:4723", options=options)
driver.get("https://m.example.com/login")
driver.find_element("id", "com.example:id/email").send_keys("test@example.com")
driver.find_element("id", "com.example:id/password").send_keys("Test1234!")
driver.find_element("id", "com.example:id/login_btn").click()

Device farms

Service Best for
BrowserStack Real devices, cross-browser, Playwright/Appium
Sauce Labs Enterprise, extensive device coverage
AWS Device Farm AWS-native, pay per minute
Firebase Test Lab Android/Flutter, Google ecosystem
Emulators/simulators Fast local dev (not production equivalent)

Mobile-specific test areas

  • Screen sizes — phone, tablet, foldable
  • OS versions — always test last 2–3 major versions
  • Orientation — portrait and landscape
  • Network conditions — 4G, 3G, offline, network switch
  • Interruptions — incoming call, low battery warning, notifications
  • Permissions — location, camera, contacts denied/granted
  • Deep links — open app from URL or push notification

Phase 11 — Certifications

Certifications aren't mandatory, but they demonstrate structured knowledge — especially valuable early in your career.

Certification Issuer Level Cost Focus
ISTQB Foundation (CTFL) ISTQB Beginner ~$200 Testing fundamentals, universal
ISTQB Advanced (CTAL) ISTQB Intermediate ~$400 Test manager, test analyst, tech test analyst
AWS Certified Cloud Practitioner AWS Beginner $100 Cloud basics — useful for cloud QA
Certified Agile Tester (CAT) iSQI Intermediate ~$300 Testing in Agile/Scrum
Selenium WebDriver with Python Udemy cert ~$15–20 Not an industry cert, but portfolio-relevant
ISTQB CTFL-AT (Agile Extension) ISTQB Intermediate ~$200 Agile testing specialization

ISTQB Foundation (CTFL) is the most recognized globally. Start here.


QA career roles and salaries

Role Description Typical salary (US)
Junior QA / Manual Tester Test case writing, bug reporting, manual E2E $45k–65k
QA Engineer Mix of manual + some automation $65k–90k
QA Automation Engineer Framework development, CI integration $85k–115k
SDET (Software Dev Engineer in Test) Deep coding skills, builds test infrastructure $100k–140k
Performance Engineer Load testing, profiling, capacity planning $90k–130k
QA Lead / Manager Team leadership, process design, stakeholder communication $100k–150k

Full technology map

QA Engineering
├── Manual testing
│   ├── Test design (EP, BVA, decision tables, state transition)
│   ├── Bug reporting (JIRA, Linear, GitHub Issues)
│   └── Exploratory testing
│
├── API testing
│   ├── Postman / Bruno / Insomnia
│   ├── REST + GraphQL + gRPC
│   └── Newman (CI runner)
│
├── UI Automation
│   ├── Selenium WebDriver
│   ├── Playwright (cross-browser, multi-lang)
│   └── Cypress (JS-native, great DX)
│
├── Mobile testing
│   ├── Appium
│   ├── Detox (React Native)
│   └── XCUITest / Espresso (native)
│
├── Performance testing
│   ├── k6
│   ├── JMeter
│   └── Locust (Python)
│
├── CI/CD
│   ├── GitHub Actions
│   ├── Jenkins
│   └── GitLab CI
│
└── Supporting skills
    ├── SQL (data verification)
    ├── Linux / command line
    ├── Docker (test environments)
    └── Git (version control)

Realistic 12-month timeline

Month Focus Milestone
1 Testing fundamentals, SDLC, ISTQB prep First bug report written
2 Manual testing, test case design 20 test cases for a real project
3 SQL basics, API concepts Can query a DB and verify data manually
4 Postman, REST API testing Full Postman collection with test scripts
5–6 Python/JS basics Can write loops, functions, handle HTTP
7–8 Selenium + pytest / POM 10 automated UI tests running locally
9 Playwright or Cypress Same tests migrated, CI pipeline running
10 k6 performance testing Load test for one critical endpoint
11 Portfolio project + ISTQB study Public GitHub repo with full test suite
12 Job applications First QA/automation role

Portfolio project idea

Build a full test suite for an open-source app (like Conduit or OpenCart demo):

qa-portfolio/
├── README.md                # What you tested, tech stack, how to run
├── test-plan.md             # Scope, approach, entry/exit criteria
├── manual/
│   └── test-cases.xlsx      # Or test-cases.md — 30+ manual test cases
├── api/
│   ├── postman/
│   │   └── collection.json  # Exported Postman collection
│   └── tests/
│       └── test_api.py      # pytest + requests
├── ui/
│   ├── pages/               # Page Object Model classes
│   └── tests/               # Playwright or Selenium tests
├── performance/
│   └── load-test.js         # k6 script
└── .github/
    └── workflows/
        └── qa.yml           # CI pipeline that runs all tests

Common mistakes QA beginners make

Mistake Why it hurts Fix
Testing only the happy path Bugs hide in edge cases Always write at least 3 negative tests per feature
Automating everything Slow suite, high maintenance Automate what's stable and high-value; explore manually
Ignoring test data management Flaky tests, random failures Use factories, seed data, reset between runs
Skipping API tests Missing a whole bug category Test API independently of UI
Brittle locators (XPath indexes) Breaks on any layout change Use data-testid attributes
No assertions — only navigation Test passes even when app is wrong Every test must assert something
Not reading error messages Slower debugging Always read the full error before Googling
Testing in production Risk of real user impact Always use staging/test environments

Frequently asked questions

Do I need to code to become a QA engineer? Not for manual testing, but automation engineering (where salaries are 30–50% higher) requires programming. Python or JavaScript is enough.

Is manual testing dead? No. Exploratory testing, usability assessment, accessibility review, and edge-case discovery still require human judgment. Automation handles repetitive regression; humans handle creative testing.

Selenium vs Playwright — which should I learn first? Playwright if you're starting from scratch in 2025. It's the modern standard. Learning Selenium is still valuable if your job uses it, but Playwright wins in new projects.

How long to get my first QA job? 6–9 months for manual QA. 9–12 months for automation roles. Start applying around month 8 — the job search itself takes time.

ISTQB — is it worth it? Yes, especially for the Foundation level. It provides a universal vocabulary, is recognized globally, and signals commitment. Cost is ~$200, and you can pass with 2–3 weeks of study.

QA vs SDET — what's the difference? QA Engineer focuses on testing strategy, test cases, and automation of test scenarios. SDET (Software Dev Engineer in Test) writes test infrastructure, frameworks, and tools — closer to a software engineer role. SDETs usually earn 15–25% more.

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools