Selenium fresher rounds check whether you can explain the basics in your own words: what Selenium is and is not, how to write a first script, XPath by hand, reading and checking elements, simple TestNG and the exceptions you will meet. Then comes a small program, like printing a table column or finding broken links, and questions about the project on your resume. It is written for final-year students, new graduates and anyone coming out of an internship or a testing course who is facing a first Selenium interview. Each question shows what the interviewer is checking, the shape of a good answer and a short answer to say out loud. Examples are in Java.
Search all questions by round, difficulty and level, or save the ones you want to practice.
One line: an open-source project for automating web browsers.
Three tools: WebDriver for coded tests, IDE for record and playback, Grid for running on many machines.
What you use: WebDriver with a language binding, plus a test runner like TestNG.
“Selenium is an open-source project for automating web browsers, mostly used to test web applications. It has three main tools. WebDriver is the one I use most: it's a library that lets my code drive a real browser, and it has bindings for Java, Python, C#, JavaScript and Ruby. Selenium IDE is a browser extension that records your clicks and plays them back, which is handy for a quick demo but not for a real suite. Selenium Grid lets you run tests on several machines and browsers at once. On its own Selenium only drives the browser, so for checks and reports I pair it with TestNG, and I use Maven to pull in the libraries. In my project that was Java, WebDriver, TestNG and Maven.”
Calling Selenium a testing tool that has assertions and reports built in, or not knowing that WebDriver is the part you code against.
Scope: only web pages inside a browser.
Hard limits: captcha, desktop apps, native mobile apps, image-level visual checks.
Missing pieces: no built-in assertions, reports or test data, so you add libraries.
“Selenium only works with web pages inside a browser, so anything outside that is out of reach. It can't automate a Windows or Mac desktop app, or a native mobile app on its own; for mobile people use Appium, which builds on the same WebDriver idea. A captcha is meant to block bots, so we either get a test bypass from the developers or test it by hand. An OTP sent to a real phone is the same story. Selenium also can't judge how a page looks, like a broken layout or a wrong image, unless you add a visual testing tool. And it has no assertions, reports or test data handling built in, which is why I pair it with TestNG and a reporting library.”
Claiming Selenium can automate anything on the screen, including captchas and desktop windows.
Protocol: talks to browsers using the W3C WebDriver standard.
New helpers: relative locators, opening a new tab or window, element screenshots.
Also: Chrome DevTools access for Chromium browsers, a redesigned Grid.
“The biggest change underneath is that Selenium 4 talks to browsers using the W3C WebDriver standard, so the commands mean the same thing in every browser. The things I've actually used are relative locators, where I can say find the input below the email field or to the right of a label, using RelativeLocator.with and methods like below, above, toLeftOf, toRightOf and near. I've also used switchTo().newWindow to open a fresh tab without a keyboard shortcut, and taking a screenshot of just one element instead of the whole page. It also added access to Chrome DevTools for Chromium browsers and a redesigned Grid. Relative locators are nice, but I still prefer a stable id when there is one.”
WebElement password = driver.findElement(
RelativeLocator.with(By.tagName("input")).below(By.id("email")));
driver.switchTo().newWindow(WindowType.TAB);
File logoShot = logo.getScreenshotAs(OutputType.FILE);
Only knowing tutorials that still set driver paths and use old APIs, with no idea anything changed.
Start: create a ChromeDriver, which launches the browser.
Act: driver.get with the URL, then driver.getTitle.
Clean up: quit inside finally so the browser never stays open.
“I create a WebDriver by calling new ChromeDriver, which opens a Chrome window. Recent Selenium 4 versions find or download the matching driver by themselves, so I don't need to set a driver path the old way. Then driver.get opens the URL and waits for the page to load, and driver.getTitle gives me the text in the title tag, which I print. I put quit in a finally block, so even if the page fails to load or an exception is thrown, the browser and the driver process are closed. In a real test I'd move the setup and quit into TestNG before and after methods and use an assertion instead of printing, but for a first script this is the whole thing.”
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class FirstTest {
public static void main(String[] args) {
WebDriver driver = new ChromeDriver();
try {
driver.get("https://example.com");
System.out.println("Title: " + driver.getTitle());
} finally {
driver.quit();
}
}
}
Forgetting to close the browser, or not knowing which class you import to start Chrome.
Locate cells: an XPath or CSS for td elements in the second position inside tbody.
Loop: findElements gives a list; print getText for each.
Watch out: header cells are th, and some tables have no tbody in the source.
“I'd use findElements with an XPath that picks the second td in every row of the table body: //table[@id='users']/tbody/tr/td[2]. That returns a list, so its size is the number of data rows, as long as every row has a second cell, and I loop over it and print getText for each cell. Using tbody skips the header row, because headers are usually th cells in thead anyway. If the table has no id I'd anchor on a nearby heading or a class. One detail worth knowing: browsers add a tbody even if the HTML doesn't have one, so the XPath still works. If I needed a whole row at a time, I'd get the tr elements first and then call findElements on each row for its cells.”
List<WebElement> rows = driver.findElements(By.xpath("//table[@id='users']/tbody/tr"));
System.out.println("Rows: " + rows.size());
List<WebElement> cells = driver.findElements(
By.xpath("//table[@id='users']/tbody/tr/td[2]"));
for (WebElement cell : cells) {
System.out.println(cell.getText());
}
Using findElement in a loop with hard-coded row numbers, or not knowing XPath indexes start at 1.
Collect: findElements on anchor tags, read each href.
Filter: skip empty, mailto and javascript links.
Check: send an HTTP request per link; a status of 400 or above means broken.
“Selenium can't see status codes; it only sees the page the browser shows. So I use Selenium to collect the links and plain Java to check them. First I find all a tags and read the href of each. I skip the ones that are empty or don't start with http, like mailto or javascript links. For each remaining URL I open an HttpURLConnection, send a HEAD request because I only need the status, and read the response code. Anything 400 or above, like a 404 Not Found or a 500 error, I print as broken. I set a timeout so one dead server doesn't hang the whole run. A few servers reject HEAD, so if I get a 405 I'd retry that link with GET before calling it broken.”
List<WebElement> links = driver.findElements(By.tagName("a"));
for (WebElement link : links) {
String href = link.getAttribute("href");
if (href == null || !href.startsWith("http")) {
continue;
}
HttpURLConnection conn = (HttpURLConnection) URI.create(href).toURL().openConnection();
conn.setRequestMethod("HEAD");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
int status = conn.getResponseCode();
if (status >= 400) {
System.out.println("Broken: " + href + " -> " + status);
}
conn.disconnect();
}
Clicking every link and checking the page title, or claiming Selenium returns the HTTP status.
Type and submit: wait for the box, clear it, sendKeys with Keys.ENTER.
Wait for results: visibility of all result titles, not a sleep.
Check: fail if there are none, then compare each title ignoring case.
“I'd wait for the search box to be clickable, clear it, and send the word with Keys.ENTER in the same sendKeys call, which presses Enter after typing. Then I'd wait until the result titles are visible instead of sleeping. That wait only returns once at least one title is showing, so an empty results page fails with a timeout rather than passing. I still keep the not-empty assert with a clear message, because a loop over zero results passes without checking anything, and that bug would come back if someone later swapped the wait for a plain findElements. Then for each title I lower-case the text and assert it contains laptop, with a message that prints the bad title. If the old results stay on screen during a new search, I'd first wait for the old list to go stale, so I don't check the old results.”
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement box = wait.until(ExpectedConditions.elementToBeClickable(By.name("q")));
box.clear();
box.sendKeys("laptop", Keys.ENTER);
List<WebElement> titles = wait.until(ExpectedConditions
.visibilityOfAllElementsLocatedBy(By.cssSelector(".result .title")));
Assert.assertFalse(titles.isEmpty(), "No results shown");
for (WebElement t : titles) {
Assert.assertTrue(t.getText().toLowerCase().contains("laptop"),
"Title without the word: " + t.getText());
}
Looping over the results without first checking the list is not empty.
Loading a URL: both open the page and wait for it to load in the same way.
The difference: navigate() also gives back, forward and refresh.
The myth: saying navigate().to() skips the wait is wrong.
“Both driver.get and driver.navigate().to open a URL, and both wait for the page to finish loading before the next line runs. In practice navigate().to is just another way to call the same thing. The real difference is that navigate gives you the browser's history buttons too: navigate().back(), navigate().forward() and navigate().refresh(). So I use get to open the first page in a test because it reads simply, and navigate when I need to go back to the list after opening a detail page, or refresh to check that a saved value is still there after reload. A lot of people say navigate doesn't wait for the page, but that's not true.”
driver.get("https://shop.example.com/products");
driver.findElement(By.linkText("Blue Kettle")).click();
driver.navigate().back(); // returns to the product list
driver.navigate().forward();
driver.navigate().refresh();
Saying navigate().to() does not wait for the page load while get() does.
getText: returns the visible text between the tags.
Inputs: what you type lives in the value property, not in the text.
Fix: read getAttribute("value"), or getDomProperty("value") in Selenium 4.
“getText returns the visible text inside an element, the text between its opening and closing tags, like the words on a button or in a paragraph. An input box has no text between tags; what you type is stored in its value property. So getText on an input comes back empty, and that's expected. To read what's in the box I call getAttribute with value, and in Selenium 4 I can also use getDomProperty with value, which reads the live property directly. I hit this in my college project when checking that a form kept the name after a page error: the check kept failing until I switched from getText to reading the value. Also, getText only returns text that's actually visible, so hidden text comes back empty as well.”
WebElement name = driver.findElement(By.id("name"));
name.sendKeys("Asha");
name.getText(); // "" for an input box
name.getAttribute("value"); // "Asha"
name.getDomProperty("value"); // "Asha"
Assuming getText is broken, or not knowing where a typed value is stored.
Three methods: isSelected for checkboxes and radios, isEnabled for buttons and fields, isDisplayed for visibility.
Trap: they need the element to exist; a missing element throws NoSuchElementException.
Checking absence: use findElements and check the list is empty.
“WebElement has three state methods. isSelected tells me whether a checkbox or radio button is ticked, isEnabled tells me whether a button or field can be used, and isDisplayed tells me whether the element is visible on the page. All three return true or false. The catch is that I have to find the element first. If it's not in the page at all, findElement throws NoSuchElementException before isDisplayed ever runs, so isDisplayed never gets the chance to return false. When I want to check that something is gone, like an error message after fixing a field, I use findElements, which returns an empty list instead of throwing, and assert the list is empty. Before ticking a checkbox I check isSelected first, so I don't untick one that's already on.”
WebElement terms = driver.findElement(By.id("terms"));
if (!terms.isSelected()) {
terms.click();
}
Assert.assertTrue(driver.findElement(By.id("submit")).isEnabled());
Assert.assertTrue(driver.findElements(By.cssSelector(".error")).isEmpty());
Believing isDisplayed returns false for an element that is not in the page at all.
Tool: the Actions class, built with the driver.
Methods: moveToElement, doubleClick, contextClick, dragAndDrop.
Must do: end with perform(), or nothing happens.
“For anything beyond a simple click I use the Actions class. I create it with new Actions(driver), then chain the gesture and finish with perform. moveToElement hovers, which I need for menus that only open on hover; after hovering I wait for the submenu and click the item. doubleClick and contextClick do a double click and a right click. dragAndDrop takes a source and a target element. The most common mistake is forgetting perform, because then the actions are only built, not sent to the browser. I used dragAndDrop in a project to move cards on a board, and learned that on some pages built with HTML5 drag and drop it doesn't work, and people fall back to a JavaScript workaround.”
Actions actions = new Actions(driver);
actions.moveToElement(accountMenu).perform();
actions.doubleClick(row).perform();
actions.contextClick(fileIcon).perform();
actions.dragAndDrop(card, doneColumn).perform();
Leaving out perform(), or trying to hover by calling click on the menu.
Methods: getCookies, getCookieNamed, addCookie, deleteCookieNamed, deleteAllCookies.
Rule: open a page on that domain before adding a cookie.
Use: add a session cookie to skip the login screen in tests that are not about login.
“Cookies are handled through driver.manage(). getCookies returns all of them, getCookieNamed returns one, addCookie adds one, and deleteCookieNamed or deleteAllCookies remove them. The rule people trip on is that you have to be on a page of that site before you can add its cookie, otherwise the browser rejects it, so I open the home page first, add the cookie, then refresh. The speed trick is logging in once, keeping the session cookie, and adding it in other tests so they start already signed in instead of typing a username and password every time. The login test itself still goes through the real form. I'd also use deleteAllCookies to start a test clean, for example to check what a new visitor sees.”
driver.get("https://shop.example.com"); // be on the domain first
driver.manage().addCookie(new Cookie("session_id", sessionToken));
driver.navigate().refresh(); // now signed in
Set<Cookie> all = driver.manage().getCookies();
driver.manage().deleteAllCookies();
Trying to add a cookie before opening any page of the site, or skipping login in the login test itself.
| Absolute | single slash from the html root, every step spelled out. |
|---|---|
| Relative | double slash, starts anywhere, anchored on a stable attribute or text. |
Why avoid absolute: one new div anywhere above the element breaks it.
“An absolute XPath starts with a single slash at the root of the page and lists every step down to the element, like /html/body/div[2]/form/div[1]/input. A relative XPath starts with a double slash, which means search anywhere, and points at the element by something that describes it, like //input[@name='email']. I avoid absolute paths because they depend on the whole page layout. If a developer adds a banner or wraps the form in one more div, every index shifts and the locator breaks, even though the email box didn't change at all. A relative XPath on a meaningful attribute survives that. When I started, I copied full XPaths from the browser tools and half my tests broke after one design change, which is how I learned this.”
Absolute: /html/body/div[2]/form/div[1]/input
Relative: //input[@name='email']
Relative: //form[@id='signup']//input[@type='email']
Saying absolute XPath is better because it is more exact.
Exact text: text()='...' or normalize-space()='...' to ignore extra spaces.
Partial text: contains(text(),'...').
Prefix: starts-with(@id,'...') for ids with a changing tail.
“For the exact button text I'd write //button[text()='Sign in']. If the HTML has spaces or a line break around the words, text() won't match, so I prefer //button[normalize-space()='Sign in'], which trims and squeezes the spaces first. For the link I'd use contains: //a[contains(text(),'Forgot')], so it matches Forgot password or Forgot your password. For the input, starts-with works on attributes: //input[starts-with(@id,'user_')]. One thing to know is that XPath is case sensitive, so contains with forgot in lower case won't match Forgot. I check each one in the browser's developer tools by searching in the Elements panel before using it, to be sure it matches exactly one element.”
//button[text()='Sign in']
//button[normalize-space()='Sign in']
//a[contains(text(),'Forgot')]
//input[starts-with(@id,'user_')]
Mixing up the syntax, like putting text() outside the brackets, or not knowing XPath is case sensitive.
Anchor: find the label by its text.
Move: following-sibling:: if they share a parent, following:: with [1] if not.
Other axes: parent::, ancestor::, preceding-sibling:: for other directions.
“I'd anchor on the label, since its text is stable, and then move to the input with an axis. If the label and input share the same parent, I'd write //label[normalize-space()='Email']/following-sibling::input. If the input is wrapped in its own div, the sibling axis won't reach it, so I'd use following:: and take the first match, //label[normalize-space()='Email']/following::input[1]. Axes let you walk in any direction: parent to go up one level, ancestor to go up several, preceding-sibling to go backwards. I'd also check if the label has a for attribute, because then the input usually has a matching id and I could use a much simpler locator. I used this in my internship because the forms were generated and had no ids at all.”
//label[normalize-space()='Email']/following-sibling::input
//label[normalize-space()='Email']/following::input[1]
//input[@type='email']/parent::div
//span[text()='Error']/ancestor::form
Falling back to an absolute XPath with indexes because the input has no attributes.
Why it worked: the element needed time to appear or become clickable.
Why it is bad: it always waits the full time, and it is still too short on a slow day.
Better: WebDriverWait until the exact condition you need.
“The sleep worked because the element wasn't ready yet, maybe the page was still loading data. But Thread.sleep always waits the full five seconds, even when the button was ready after half a second, so across a hundred steps the suite gets very slow. And on a slow test server five seconds might not be enough, so the test still fails sometimes. Instead I use an explicit wait: a WebDriverWait with a timeout, and ExpectedConditions.elementToBeClickable for that button. It checks again and again and moves on as soon as the button is ready, and only fails if it's still not ready after the full timeout. So it's both faster and more reliable. In my project, replacing sleeps with waits cut the run time roughly in half.”
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement pay = wait.until(ExpectedConditions.elementToBeClickable(By.id("pay")));
pay.click();
Defending Thread.sleep as fine because it makes the test pass.
NoSuchElementException: the locator matched nothing, or you looked too early or in the wrong frame.
TimeoutException: an explicit wait ran out before the condition came true.
ElementNotInteractableException: the element exists but is hidden or cannot take input.
“The first one everyone meets is NoSuchElementException. findElement throws it when the locator matches nothing, and for me the cause was usually a typo in the locator, looking before the page loaded, or the element being inside an iframe. The second is TimeoutException, which comes from an explicit wait when the condition still isn't true after the timeout, so it tells me the page never reached the state I expected. The third is ElementNotInteractableException: the element is in the page, but it's hidden or covered, so Selenium can't type into it. I got that once because the page had two email fields, one hidden for mobile, and my locator picked the hidden one. I also saw NoAlertPresentException when I tried to switch to an alert that hadn't opened yet.”
Only being able to name exceptions without saying what caused them, or catching every exception and ignoring it.
| Hard assert | Assert class, stops the test at the first failure. |
|---|---|
| Soft assert | SoftAssert object, collects failures and keeps going. |
The catch: call assertAll() at the end, or failures are never reported.
“A hard assert, from the Assert class, stops the test the moment a check fails, and the rest of the method doesn't run. That's right when later steps depend on it, like if login fails there's no point checking the dashboard. A soft assert uses a SoftAssert object: each failed check is recorded and the test carries on, so in one run I can see every wrong field on a profile page instead of fixing them one at a time. The important part is calling assertAll at the end. If I forget it, the test passes even with failures, because the collected failures are never thrown. I also create a new SoftAssert inside each test, because sharing one across tests mixes their failures together.”
SoftAssert soft = new SoftAssert();
soft.assertEquals(nameField.getDomProperty("value"), "Asha");
soft.assertEquals(emailField.getDomProperty("value"), "asha@example.com");
soft.assertTrue(saveButton.isEnabled(), "Save should be enabled");
soft.assertAll(); // throws once, listing every failure above
Not knowing about assertAll, or saying soft asserts are always better.
Default: methods in a class run in alphabetical order of their names.
Order: priority, lowest number first.
Depend and skip: dependsOnMethods skips the test if the first one fails; enabled = false turns it off.
“By default TestNG doesn't run methods in the order I wrote them; within a class it runs them in alphabetical order of the method names. To force an order I use the priority attribute, and the lowest number runs first; tests without one get priority zero. If one test really needs another to pass first, like adding to cart needs login, I use dependsOnMethods. Then if login fails, TestNG marks the cart test as skipped instead of failed, which makes the report clearer about the real cause. To turn off a test for now without deleting it, I set enabled to false. I try not to chain too many tests, though, because one failure then skips a lot of them.”
@Test(priority = 1)
public void login() { }
@Test(priority = 2, dependsOnMethods = "login")
public void addToCart() { }
@Test(enabled = false)
public void applyCoupon() { }
Assuming TestNG runs methods top to bottom in the order they appear in the file.
Purpose: defines a suite, which classes and tests to run.
Common pieces: suite, test, classes, groups to include or exclude, parameters.
How it runs: from the IDE or through Maven.
“testng.xml describes a test suite: which test classes to run and how. The top tag is suite, inside it one or more test blocks, and inside those the list of classes. In my project I used it for three things. First, to list the classes for the regression run. Second, to pick groups, so I could run only the tests marked smoke before a demo. Third, to pass a parameter for the browser name, which my setup method read with the @Parameters annotation, so the same tests could run on Chrome or Firefox without changing code. I ran it from the IDE while writing tests, and through Maven with the surefire plugin pointing at the file so it could run from the command line too.”
<suite name="Smoke">
<test name="Chrome run">
<parameter name="browser" value="chrome"/>
<groups><run><include name="smoke"/></run></groups>
<classes>
<class name="tests.LoginTest"/>
<class name="tests.CartTest"/>
</classes>
</test>
</suite>
Never having seen the file and only ever running single classes from the IDE.
Context: the app, the team size, your part.
What you automated: the flows and roughly how many tests.
Structure and lesson: folders, page classes, TestNG, and one thing you would do differently.
“In my final-year project I automated tests for a small online bookstore that our team of four built. I owned the testing side. I wrote about twenty-five tests covering sign up, login, search, the cart and checkout with a dummy payment. I used Java, Selenium WebDriver and TestNG, with Maven for dependencies. I kept one class per page, with its locators and actions, so the tests read like steps: log in, search, add to cart. Test data like usernames sat in a properties file, and I took a screenshot whenever a test failed. The tests caught two real bugs, including the cart total not updating after removing an item. If I did it again, I'd use explicit waits from day one, because I spent a week removing sleeps later.”
Listing tools with no detail on what you personally wrote, or being unable to describe your own folder structure.
Situation: what failed and the error you saw.
Checks: repeat it by hand, look at the screenshot, test the locator in the browser.
Result: what it turned out to be and what you did about it.
“During my internship a checkout test started failing with a TimeoutException waiting for the order confirmation. My first thought was that my wait was too short, but instead of just raising it I did the same steps by hand. The confirmation never appeared manually either; the page showed a spinner forever when the cart had a discounted item. So I checked the failure screenshot, saw the same spinner, and tried a cart with no discount, which worked. That told me it was the app, not my script. I wrote a bug report with the exact steps, the test data and the screenshot, and the developer found a crash in the discount calculation. I learned to reproduce by hand before touching a failing test.”
Increasing timeouts or deleting the check until the test goes green, without finding out why it failed.
Read the error: the first lines usually name the problem.
Versions and paths: browser vs driver version, hard-coded driver or file paths.
Settings: URLs, test data, screen size and Java version, then fix it for everyone.
“First I'd read the actual error on their machine, because failing at startup usually means the browser never opened. The most common cause is a driver that doesn't match their browser version, or a hard-coded path like my own C drive folder for chromedriver, which doesn't exist on their laptop. On recent Selenium 4 I'd remove the manual driver path and let Selenium manage it. Next I'd look for other things tied to my machine: a base URL pointing at my localhost, test data files with my folder path, or a different Java version. If tests fail later rather than at startup, I'd check screen size, since a smaller window can hide elements. Then I'd fix it in the code, like moving settings into a config file, so it doesn't happen to the next person.”
Telling the teammate to copy your exact setup instead of removing what ties the code to your machine.
Ask first: test accounts, which environment, and whether there is a captcha or OTP.
First tests: valid login, wrong password, empty fields.
Next: locked account, logout, forgot password link, password field masked.
“Before writing code I'd ask three things: which environment I should test on, whether there are test accounts I'm allowed to use, and whether there's a captcha or an OTP, because I can't automate those and would need a test bypass. Then I'd start with the cases that matter most. A valid login lands on the dashboard. A wrong password shows the right error and doesn't log in. Empty username or password shows a validation message. After those, I'd add a locked or disabled account, logout, the forgot password link opening the right page, and checking that the password field hides what I type. I'd keep test data out of the code, and show my lead the first three tests before writing the rest, so I learn their standards early.”
Starting to code without asking about test accounts or captcha, or only testing the happy path.
ClapAssist is an AI interview assistant for Mac and Windows. It listens to the interview on your computer and shows you what to say, in short lines you can read while you talk. Your live interview audio and screen are never stored. Your resume and notes are saved to your account so the app fills them in on any computer. It stays out of screen share on every plan, including Free; only you can see it.