Automation Languages
In-depth theory of the languages used in test automation. Pick a language to study how it works, its core concepts, and how QA engineers write Selenium / Playwright / Cypress (or Robot) with it.
What is Python?
Python is a high-level, interpreted, dynamically typed language created by Guido van Rossum (first released in 1991). It emphasizes readability — “code is read more often than it is written.” That makes it one of the most popular choices for test automation, scripting, API testing, and data validation.
How Python works (theory)
- Interpreted: Source runs through the CPython interpreter (bytecode → Virtual Machine). You do not compile a .exe for normal scripts.
- Dynamic typing: Types are checked at runtime. A variable can hold an int, then later a string (unless you add type hints + tools).
- Everything is an object: Numbers, strings, functions, and classes are all objects with identity, type, and value.
- Indentation is syntax: Blocks are defined by spaces (PEP 8 recommends 4 spaces), not braces
{}. - Garbage collected: Memory is managed automatically (reference counting + cyclic GC).
Core building blocks
1. Variables & types
Common built-in types: int, float, bool, str, list, tuple, dict, set, None.
name = "tester" # str
age = 28 # int
passed = True # bool
roles = ["QA", "SDET"] # list (mutable)
point = (10, 20) # tuple (immutable)
user = {"id": 1, "name": "Alex"} # dict
print(type(name), len(roles))
2. Operators & truthiness
Python treats empty values as falsy: 0, "", [], {}, None, False. Everything else is truthy. Comparison uses == (value) vs is (identity). Prefer == for strings and numbers.
3. Control flow
status = "success"
if status == "success":
print("PASS")
elif status == "pending":
print("WAIT")
else:
print("FAIL")
for role in ["Admin", "User"]:
print(role)
count = 0
while count < 3:
count += 1
4. Functions
Functions are first-class (you can pass them as arguments). Use default args, *args, **kwargs, and return values.
def login(username, password="password123"):
"""Return True if credentials look valid."""
return bool(username and password)
assert login("tester") is True
5. Modules & packages
Reuse code with import. Automation projects usually split pages, tests, and utilities into modules.
from selenium.webdriver.common.by import By
import time
6. OOP for Page Objects
Classes group state + behavior. In automation, a Page Object class stores locators and actions for one page/section.
class LoginPage:
def __init__(self, driver):
self.driver = driver
def login(self, user, password):
self.driver.find_element(By.ID, "login-username").send_keys(user)
self.driver.find_element(By.ID, "login-password").send_keys(password)
self.driver.find_element(By.ID, "login-submit").click()
7. Exceptions (error handling)
Selenium raises exceptions like NoSuchElementException. Catch what you expect; fail fast on unexpected errors.
from selenium.common.exceptions import NoSuchElementException
try:
driver.find_element(By.ID, "missing").click()
except NoSuchElementException:
print("Element not found — check locator or wait")
Python mental model for automation
- Launch / connect to a browser (driver or page).
- Navigate to a URL / section.
- Locate elements (
By.ID, CSS, XPath…). - Act (click, type, select) and wait for readiness.
- Assert outcomes (
assert, pytest, unittest). - Clean up (quit browser, close files).
Ecosystem for QA
- Selenium WebDriver — classic cross-browser UI automation
- Playwright for Python — modern auto-waits, tracing, multi-browser
- pytest — test runner, fixtures, parametrize, plugins
- Robot Framework — keyword-driven layer on top of Python
- requests — API testing alongside UI
Selenium (Python) example
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://qapracticehub.com/#forms")
driver.find_element(By.ID, "login-username").send_keys("tester")
driver.find_element(By.ID, "login-password").send_keys("password123")
driver.find_element(By.ID, "login-submit").click()
msg = driver.find_element(By.ID, "login-message").text
print(msg)
assert "success" in msg.lower()
driver.quit()
Playwright (Python) example
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://qapracticehub.com/#forms")
page.locator("#login-username").fill("tester")
page.locator("#login-password").fill("password123")
page.locator("#login-submit").click()
print(page.locator("#login-message").inner_text())
browser.close()
Best practices in Python automation
- Follow PEP 8; keep functions small and named by behavior (
submit_login). - Use explicit waits (or Playwright auto-wait) — avoid fixed
time.sleepexcept debugging. - Store locators in Page Objects / constants, not scattered strings.
- Use virtual environments (
venv) and pin dependencies (requirements.txt). - Prefer
pytestfixtures for driver setup/teardown.
Practice driver / By steps in Try it Yourself.
What is Java?
Java is a statically typed, compiled, object-oriented language (Sun/Oracle, 1995) that runs on the JVM (Java Virtual Machine). “Write once, run anywhere” — you compile to bytecode (.class), then the JVM executes it on any OS with a compatible JRE.
How Java works (theory)
- Compile then run:
.java→javac→ bytecode → JVM. - Static typing: Every variable has a declared type. Type errors are caught at compile time.
- Strong OOP: Classes, interfaces, inheritance, polymorphism are central.
- Garbage collection: Heap memory is managed by the GC; you rarely free memory manually.
- Packages: Code is organized in namespaces (
com.company.tests).
Core building blocks
1. Types & variables
Primitives: int, long, double, boolean, char… Reference types: String, arrays, your classes, collections.
String name = "tester";
int attempts = 3;
boolean passed = true;
String[] roles = {"QA", "SDET"};
2. Control flow
if (passed) {
System.out.println("PASS");
} else {
System.out.println("FAIL");
}
for (int i = 0; i < roles.length; i++) {
System.out.println(roles[i]);
}
for (String role : roles) {
System.out.println(role);
}
3. Methods & access modifiers
public, private, protected, package-private control visibility — important for clean Page Object APIs.
public class MathUtil {
public static int add(int a, int b) {
return a + b;
}
}
4. Classes & objects
A class is a blueprint; an object is an instance. Automation frameworks in Java are almost always class-based (TestNG/JUnit test classes + page classes).
public class LoginPage {
private WebDriver driver;
public LoginPage(WebDriver driver) {
this.driver = driver;
}
public void login(String user, String password) {
driver.findElement(By.id("login-username")).sendKeys(user);
driver.findElement(By.id("login-password")).sendKeys(password);
driver.findElement(By.id("login-submit")).click();
}
}
5. Interfaces & polymorphism
WebDriver is an interface; ChromeDriver, FirefoxDriver implement it. You program to the interface so tests stay browser-agnostic.
WebDriver driver = new ChromeDriver(); // ChromeDriver implements WebDriver
6. Exceptions
Checked vs unchecked exceptions. Selenium’s NoSuchElementException is unchecked (RuntimeException). Use try/catch/finally or try-with-resources for drivers.
try {
driver.findElement(By.id("missing")).click();
} catch (NoSuchElementException e) {
System.out.println("Locator failed: " + e.getMessage());
} finally {
driver.quit();
}
Collections you will use in tests
List<WebElement>— multiple matches fromfindElementsMap<String, String>— test data rowsSet— unique values
Ecosystem for QA
- Selenium Java — industry standard for many enterprises
- TestNG / JUnit 5 — annotations, assertions, parallel runs
- Maven / Gradle — build & dependency management
- Playwright Java — modern alternative
- Rest Assured — API automation in Java
Selenium (Java) example
WebDriver driver = new ChromeDriver();
driver.get("https://qapracticehub.com/#forms");
driver.findElement(By.id("login-username")).sendKeys("tester");
driver.findElement(By.id("login-password")).sendKeys("password123");
driver.findElement(By.id("login-submit")).click();
String msg = driver.findElement(By.id("login-message")).getText();
System.out.println(msg);
Assert.assertTrue(msg.toLowerCase().contains("success"));
driver.quit();
Best practices in Java automation
- Use Page Object Model + clear packages (
pages,tests,utils). - Prefer explicit waits (
WebDriverWait+ ExpectedConditions). - Keep tests independent; don’t rely on execution order.
- Use a WebDriverManager / Selenium Manager so drivers stay updated.
- Fail with meaningful assert messages.
What is JavaScript?
JavaScript (ECMAScript) is the language of the web browser and, via Node.js, of server-side tooling. It is dynamically typed, prototype-based, and event-driven. Cypress is JavaScript-native; Playwright and WebdriverIO also shine in JS.
How JavaScript works (theory)
- Single-threaded + event loop: One call stack, but async I/O via callbacks, Promises, and
async/await. - Dynamic typing: Types exist at runtime (
typeof, duck typing). - Objects everywhere: Objects are key–value maps; arrays are specialized objects.
- Lexical scope & closures: Inner functions remember outer variables — useful in helpers and fixtures.
- Modules: Modern code uses
import/export(ESM) orrequire(CommonJS).
Core building blocks
1. Variables: let, const, var
Prefer const by default, let when reassignment is needed. Avoid var (function-scoped, hoisting surprises).
const baseUrl = "https://qapracticehub.com";
let retries = 2;
retries += 1;
2. Types & equality
Primitives: number, string, boolean, null, undefined, symbol, bigint. Use === (strict) not == (coercion).
console.log(1 === "1"); // false
console.log(Boolean("")); // false — empty string is falsy
3. Functions & arrow functions
function add(a, b) {
return a + b;
}
const multiply = (a, b) => a * b;
4. Arrays & objects
const users = ["Alex", "Sam"];
users.push("Jordan");
const user = { id: 1, role: "QA" };
const { role } = user; // destructuring
5. Asynchronous JavaScript (critical for automation)
Browser automation is async: navigation, clicks, and network take time. Promises represent future values; async/await makes them readable.
async function openForms(page) {
await page.goto("https://qapracticehub.com/#forms");
await page.locator("#login-username").fill("tester");
}
Cypress looks synchronous in tests, but commands are queued on its own async command chain.
6. Error handling
try {
await page.locator("#missing").click();
} catch (err) {
console.error("Step failed:", err.message);
}
Ecosystem for QA
- Cypress — DX-focused E2E, time-travel UI, JS/TS
- Playwright Test — parallel, trace viewer, multi-browser
- WebdriverIO — Selenium protocol + modern syntax
- Jest / Mocha — unit and component test runners
- npm / yarn / pnpm — package management
Playwright (JavaScript)
const { chromium } = require("playwright");
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto("https://qapracticehub.com/#forms");
await page.locator("#login-username").fill("tester");
await page.locator("#login-password").fill("password123");
await page.locator("#login-submit").click();
console.log(await page.locator("#login-message").innerText());
await browser.close();
})();
Cypress (JavaScript)
describe("Login form", () => {
it("logs in successfully", () => {
cy.visit("https://qapracticehub.com/#forms");
cy.get("#login-username").type("tester");
cy.get("#login-password").type("password123");
cy.get("#login-submit").click();
cy.get("#login-message").should("be.visible");
});
});
Best practices in JavaScript automation
- Always
awaitPlaywright calls; missing await causes flaky races. - Prefer role/label/test-id locators over brittle CSS chains.
- Keep custom commands (Cypress) or fixtures small and documented.
- Use ESLint; enable strict equality and no-floating-promises rules.
- Understand the event loop enough to debug flaky async tests.
What is TypeScript?
TypeScript is a typed superset of JavaScript created by Microsoft. It adds a static type system that erases to plain JavaScript at compile time (tsc). Playwright’s official docs and scaffolding prefer TypeScript because locators, fixtures, and page objects become safer and easier to autocomplete.
How TypeScript works (theory)
- Compile-time checks: Wrong property names or argument types fail before the test runs.
- Structural typing: Compatibility is based on shape (“duck typing with types”), not nominal class names only.
- Gradual typing: You can start loose and tighten (
strictmode intsconfig.json). - Emits JavaScript: Runtime is still JS/Node — types do not exist at runtime unless you add extra libraries.
Core type system ideas
1. Basic annotations
const username: string = "tester";
let attempts: number = 3;
const passed: boolean = true;
function greet(name: string): string {
return `Hello ${name}`;
}
2. Interfaces & type aliases
Model test data and page contracts with interfaces.
interface UserCredentials {
username: string;
password: string;
}
const admin: UserCredentials = {
username: "tester",
password: "password123",
};
3. Union types & literals
type BrowserName = "chromium" | "firefox" | "webkit";
function launch(browser: BrowserName) {
return browser;
}
4. Generics
Reusable typed helpers (e.g., API clients, data tables).
function first<T>(items: T[]): T | undefined {
return items[0];
}
5. Optional chaining & nullish coalescing
const label = user.profile?.title ?? "N/A";
Why TypeScript helps automation
- Page Object methods show required arguments in the IDE.
- Refactors rename safely across large suites.
- Fewer “undefined is not a function” runtime surprises.
- Shared types between UI tests and API contracts.
Playwright Test (TypeScript)
import { test, expect, Page } from "@playwright/test";
class LoginPage {
constructor(private page: Page) {}
username = this.page.locator("#login-username");
password = this.page.locator("#login-password");
submit = this.page.locator("#login-submit");
async login(user: string, pass: string) {
await this.username.fill(user);
await this.password.fill(pass);
await this.submit.click();
}
}
test("login form", async ({ page }) => {
const login = new LoginPage(page);
await page.goto("https://qapracticehub.com/#forms");
await login.login("tester", "password123");
await expect(page.locator("#login-message")).toBeVisible();
});
Modern locators
page.getByRole("button", { name: "Login" })
page.getByLabel("Email")
page.getByPlaceholder("Search...")
page.getByTestId("email-input")
page.locator("xpath=//input[@id='email-input']")
Best practices in TypeScript automation
- Enable
"strict": trueintsconfig.json. - Type Page Objects and test fixtures; avoid
any. - Keep runtime validation for external data (API/env) — types alone are not enough.
- Prefer Playwright’s built-in assertions (
expect) for auto-retry. - Commit
package-lock/pnpm-lockfor reproducible CI.
What is Playwright with TypeScript?
Playwright is a modern end-to-end testing framework from Microsoft that automates Chromium, Firefox, and WebKit.
TypeScript is the recommended language for Playwright Test: you get typed page fixtures, autocomplete for locators,
and safer Page Objects. Together they are one of the most popular stacks for new UI automation projects.
Why this combination?
- Auto-waiting: Playwright waits for actionability (visible, stable, enabled) before clicks/fills.
- Multi-browser: One API for Chromium, Firefox, and WebKit.
- TypeScript DX: Compile-time checks on selectors helpers, fixtures, and test data.
- Built-in runner:
@playwright/testincludes parallel runs, retries, trace viewer, and HTML report. - Modern locators:
getByRole,getByTestId,getByLabelencourage accessible, stable tests.
Core concepts you must know
1. test & expect
Every case is a test(). Assertions use Playwright’s expect, which retries until timeout — unlike a plain Node assert.
import { test, expect } from "@playwright/test";
test("message is visible", async ({ page }) => {
await page.goto("https://qapracticehub.com/#forms");
await expect(page.locator("#login-message")).toBeHidden();
});
2. Fixtures (page, context, browser)
Playwright injects a fresh page per test by default (isolated context). You can extend fixtures for logged-in states.
3. Locators (lazy + auto-wait)
A locator is a query, not a one-time WebElement snapshot. Actions on it wait automatically.
page.getByTestId("login-username")
page.getByRole("button", { name: "Login" })
page.locator("#login-password")
page.locator("xpath=//input[@id='login-username']")
4. async / await (required)
Almost every Playwright API returns a Promise. Forgetting await is the #1 cause of flaky or empty tests.
5. TypeScript project shape
playwright.config.ts— browsers, baseURL, retries, reporterstests/**/*.spec.ts— test filespages/*.ts— typed Page Objectstsconfig.json— usually strict mode
Setup (mental model)
npm init playwright@latest
# choose TypeScript when prompted
npx playwright test
npx playwright show-report
Full login example (Playwright + TypeScript)
import { test, expect, Page } from "@playwright/test";
class LoginPage {
readonly username = this.page.getByTestId("login-username");
readonly password = this.page.getByTestId("login-password");
readonly submit = this.page.getByTestId("login-submit");
readonly message = this.page.getByTestId("login-message");
constructor(private readonly page: Page) {}
async open() {
await this.page.goto("https://qapracticehub.com/#forms");
}
async login(user: string, pass: string) {
await this.username.fill(user);
await this.password.fill(pass);
await this.submit.click();
}
}
test.describe("QA Practice Hub — Forms", () => {
test("successful login", async ({ page }) => {
const login = new LoginPage(page);
await login.open();
await login.login("tester", "password123");
await expect(login.message).toBeVisible();
await expect(login.message).toContainText(/success/i);
});
});
Useful Playwright + TS patterns
Config with baseURL
import { defineConfig } from "@playwright/test";
export default defineConfig({
testDir: "./tests",
use: {
baseURL: "https://qapracticehub.com",
trace: "on-first-retry",
},
retries: 1,
});
Parametrize with test.describe
const users = [
{ user: "tester", pass: "password123" },
] as const;
for (const row of users) {
test(`login as ${row.user}`, async ({ page }) => {
await page.goto("/#forms");
await page.getByTestId("login-username").fill(row.user);
await page.getByTestId("login-password").fill(row.pass);
await page.getByTestId("login-submit").click();
await expect(page.getByTestId("login-message")).toBeVisible();
});
}
Debugging & quality
npx playwright test --debug— step through actionsawait page.pause()— inspector mid-test- Trace viewer on failure — timeline of DOM, network, screenshots
- Prefer user-facing locators; fall back to
data-testidon QA Practice Hub
Best practices
- One assertion intent per test; keep tests independent.
- Type Page Objects; avoid
any. - Never mix raw Selenium-style sleeps — use expect auto-retry or
waitFor. - Commit lockfile; install browsers in CI with
npx playwright install --with-deps. - Practice flows first on QA Practice Hub, then on your app.
You can also try Playwright-style page.locator steps in Try it Yourself.
What is C#?
C# (“C-sharp”) is a statically typed, object-oriented language from Microsoft that runs on .NET. It is common in enterprises already invested in Windows/.NET stacks, with first-class Selenium and Playwright bindings and strong Visual Studio tooling.
How C# works (theory)
- Compiled to IL: Source compiles to Intermediate Language, executed by the .NET runtime (CLR) with JIT compilation.
- Static typing + modern features: Generics, LINQ, async/await, nullable reference types.
- OOP + components: Classes, interfaces, records, dependency injection patterns in larger frameworks.
- Garbage collected: Memory managed by the CLR GC.
- Namespaces & assemblies: Organize code similarly to Java packages.
Core building blocks
1. Types & variables
string name = "tester";
int attempts = 3;
bool passed = true;
var roles = new List<string> { "QA", "SDET" }; // type inferred
2. Properties & classes
public class LoginPage
{
private readonly IWebDriver _driver;
public LoginPage(IWebDriver driver) => _driver = driver;
public void Login(string user, string password)
{
_driver.FindElement(By.Id("login-username")).SendKeys(user);
_driver.FindElement(By.Id("login-password")).SendKeys(password);
_driver.FindElement(By.Id("login-submit")).Click();
}
}
3. Control flow & LINQ
LINQ makes filtering test data and DOM-derived lists expressive.
var active = roles.Where(r => r.StartsWith("Q")).ToList();
4. async / await
Playwright for .NET is asynchronous. Selenium classic APIs are mostly synchronous, but modern .NET tests often mix async I/O.
await page.GotoAsync("https://qapracticehub.com/#forms");
await page.Locator("#login-username").FillAsync("tester");
5. Exceptions
try
{
_driver.FindElement(By.Id("missing")).Click();
}
catch (NoSuchElementException ex)
{
Console.WriteLine(ex.Message);
}
Ecosystem for QA
- Selenium WebDriver .NET
- NUnit / xUnit / MSTest — test frameworks
- Playwright .NET
- SpecFlow — BDD (Gherkin) on .NET
- NuGet — packages
Selenium (C#) example
IWebDriver driver = new ChromeDriver();
driver.Navigate().GoToUrl("https://qapracticehub.com/#forms");
driver.FindElement(By.Id("login-username")).SendKeys("tester");
driver.FindElement(By.Id("login-password")).SendKeys("password123");
driver.FindElement(By.Id("login-submit")).Click();
string msg = driver.FindElement(By.Id("login-message")).Text;
Console.WriteLine(msg);
Assert.That(msg.ToLower().Contains("success"));
driver.Quit();
Playwright (C#) example
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync();
var page = await browser.NewPageAsync();
await page.GotoAsync("https://qapracticehub.com/#forms");
await page.Locator("#login-username").FillAsync("tester");
await page.Locator("#login-password").FillAsync("password123");
await page.Locator("#login-submit").ClickAsync();
Console.WriteLine(await page.Locator("#login-message").InnerTextAsync());
Best practices in C# automation
- Use interfaces (
IWebDriver) and constructor injection for pages. - Enable nullable reference types to catch null bugs early.
- Prefer explicit waits; centralize timeout configuration.
- Keep test data outside code when possible (JSON, runsettings).
- Run on CI with deterministic browser install (Playwright) or Selenium Manager.
What is Robot Framework?
Robot Framework is an open-source keyword-driven acceptance-test and RPA framework. The core is written in Python, but you author tests in a tabular, human-readable syntax. Libraries (SeleniumLibrary, Browser, RequestsLibrary, …) provide the keywords that talk to browsers and APIs.
How Robot works (theory)
- Keyword-driven: Tests call named actions (
Click Button) instead of raw programming loops — though you can extend with Python. - Data-driven friendly: Templates and Test Template / [Template] run the same flow with many data rows.
- Layered architecture: Test cases → keywords (user or library) → drivers/libraries → application under test.
- Rich reports: Each run produces log.html / report.html with step-level detail.
- Variables everywhere:
${SCALARS},@{LISTS},&{DICTS}, environment overrides.
File structure & sections
*** Settings ***— libraries, resources, documentation, tags*** Variables ***— suite-level data*** Test Cases ***— scenarios*** Keywords ***— reusable user keywords (like functions)
*** Settings ***
Library SeleniumLibrary
Resource ../resources/common.resource
*** Variables ***
${URL} https://qapracticehub.com/#forms
${BROWSER} chrome
*** Test Cases ***
Example
Log Hello from Robot
Variables & arguments (deep knowledge)
*** Variables ***
${USER} tester
@{ITEMS} apple banana
&{CRED} user=tester pass=password123
*** Keywords ***
Login With
[Arguments] ${username} ${password}
Input Text id:login-username ${username}
Input Text id:login-password ${password}
Click Button id:login-submit
Return values use ${msg}= Get Text id:login-message. Space-separated (or pipe-separated) tables are how arguments are delimited — two or more spaces between cells.
Control flow in Robot
Run Keyword If/IF/ELSE(modern IF/ELSE syntax)FORloops over listsWait Until Keyword Succeedsfor retry-style waitsRun Keywordsto group setup steps
IF '${status}' == 'success'
Log PASS
ELSE
Fail Unexpected status
END
FOR ${item} IN @{ITEMS}
Log ${item}
END
Locators in SeleniumLibrary
Strategy prefixes: id:, name:, css:, xpath:, link:. Consistency matters — pick one style per project.
id:login-username
css:#email-input
xpath://button[@id='login-submit']
css:[data-testid='login-username']
Full login example
*** Settings ***
Library SeleniumLibrary
*** Test Cases ***
Login Form Success
Open Browser https://qapracticehub.com/#forms chrome
Wait Until Element Is Visible id:login-username
Input Text id:login-username tester
Input Text id:login-password password123
Click Button id:login-submit
${msg}= Get Text id:login-message
Log ${msg}
Should Contain ${msg} success
Close Browser
Assertions & reporting
Should Be Equal,Should Contain,Should Be TruePage Should Contain,Element Should Be Visible- Tags (
[Tags] smoke) for selecting suites in CI - Fail keyword stops the case with a message
When to choose Robot
- Manual testers need to read/write cases with minimal code.
- You want standardized reporting out of the box.
- You can invest in a solid resource/keyword layer (otherwise suites become copy-paste).
Best practices
- Push complexity into user keywords / Python libraries — keep cases short.
- One suite responsibility; use resources for shared steps.
- Prefer explicit waits (
Wait Until Element Is Visible) overSleep. - Name keywords as business actions:
Submit Login Form, notClick xpath1. - Version libraries; run headless in CI with consistent browser versions.
Practice Robot-style keywords in Try it Yourself (Input Text, Click Button, Get Text, …).