Here is something that confuses everyone arriving from Cypress or Playwright.
Selenium has no assertion library. None. There is no expect, no should. Search the documentation and you will not find one.
That is not an omission. Selenium's job is to control a browser. Deciding whether a value is correct is a different job, and it was left to the tools that already did it well.
So a Selenium project has two halves you assemble yourself:
- Selenium WebDriver finds elements and does things to them.
- A test framework and assertion library structures the tests and checks the results.
In JavaScript that is usually Mocha or Jest. In Java, JUnit or TestNG with AssertJ. In Python, pytest.
A complete test looks like this:
const { expect } = require('chai');
const { Builder, By } = require('selenium-webdriver');
describe('cart', () => {
it('shows the item count in the badge', async () => {
const driver = await new Builder().forBrowser('chrome').build();
try {
await driver.get('http://localhost:3000/shop');
await driver.findElement(By.css('[data-testid="add-to-cart"]')).click();
const badge = await driver.findElement(By.css('[data-testid="cart-badge"]'));
const text = await badge.getText();
expect(text).to.equal('1');
} finally {
await driver.quit();
}
});
});
describe and it come from Mocha. expect comes from Chai. Only Builder, By and the driver calls are Selenium.
Now the consequence, and it is the single most important difference between Selenium and the other two tools.
⚠️ Your assertions do not retry. Ever.
In Cypress and Playwright, an assertion keeps re-checking until it passes or times out. That is a feature of those tools, not of testing in general.
expect(text).to.equal('1')compares two strings once. If the badge had not updated yet, the comparison fails immediately, and no amount of patience from the assertion library will help, because it is not watching anything.
This is why waiting gets so much attention in Selenium. The pattern you must internalise is: wait first, then read, then assert.
await driver.wait(until.elementTextIs(badge, '1'), 5000);
const text = await badge.getText();
expect(text).to.equal('1');
The wait does the retrying. By the time you read the text, the value is already what you expect, and the assertion is just a final confirmation.
Get this order wrong and you get flaky tests. Get it right and Selenium is as reliable as anything else. Nearly every unstable Selenium suite I have seen was reading values before waiting for them.
Remember this: Selenium finds, your library asserts, and the wait between them is your responsibility.
Reference: Selenium with test runners.