Writing tests
Introduction
CSTesting tests are simple: they perform actions (navigate, click, type) and assert state with expect().
You use the test runner (describe, it), create a browser with createBrowser(), then call methods like goto(), click(), type(), and locator(selector). For elements that may appear after a delay, use waitForSelector() before acting. Assertions use expect(value) with matchers such as toBe, toEqual, and toContain — use them on values you already have (e.g. from content(), url(), or locator().textContent()).
You will learn
- How to write the first test
- How to perform actions (navigation, interactions, basic actions)
- How to use assertions
- How tests run in isolation
- How to use test hooks
First test
Example: open a page, then check the title and click a link.
tests/example.test.js
const { describe, it, expect, beforeAll, afterAll, createBrowser } = require('cstesting');
describe('Example site', () => {
let browser;
beforeAll(async () => {
browser = await createBrowser({ headless: true });
});
afterAll(async () => {
if (browser) await browser.close();
});
it('has title', async () => {
await browser.goto('https://example.com');
const title = await browser.evaluate("document.title");
expect(title).toContain('Example');
});
it('click link and check content', async () => {
await browser.goto('https://example.com');
await browser.locator('a').click();
await browser.waitForLoad();
const html = await browser.content();
expect(html.length).toBeGreaterThan(0);
});
});
Add // @ts-check at the top of the file when using JavaScript in VS Code to get better type checking (if your environment supports it).
Actions
Navigation
Most tests start by opening a URL. Then you interact with the page.
await browser.goto('https://example.com');
CSTesting waits for the page load to complete before continuing. After that, use click(), type(), locator(), etc.
Interactions
Actions start by locating elements. Use selectors (CSS, or shorthand like #id, name="q") or the Locators API.
Direct calls (selector + optional index):
await browser.click('button');
await browser.type('#email', 'user@test.com');
await browser.locator('a').click();
Locators represent an element (or a specific one when there are many). You can call .click(), .type(text), .check(), .uncheck(), .select(option) on a locator. Use .first(), .last(), or .nth(n) when multiple elements match.
const link = browser.locator('a');
await link.click();
For elements that appear after loading (e.g. dynamic content), wait first:
await browser.waitForSelector('.results');
await browser.click('.results .item');
Basic actions
| Action | Description |
|---|---|
browser.goto(url) | Navigate to URL |
browser.click(selector) | Click the element |
locator.click() | Click the locator's element |
browser.type(selector, text) | Type text into the field |
locator.type(text) | Type into the locator's element |
locator.check() | Check a checkbox or radio |
locator.uncheck() | Uncheck a checkbox |
locator.hover() | Hover over the element |
locator.dragTo(targetSelector) | Drag to another element |
locator.select(option) | Select option(s) in a <select> (e.g. { label: 'One' }, { value: '1' }, { index: 0 }) |
browser.pressKey(key) | Press a key (e.g. 'Enter', 'Tab') |
locator.pressKey(key) | Press key (on the focused element) |
browser.waitForSelector(selector, { timeout }) | Wait until selector matches (default timeout applies) |
browser.waitForLoad() | Wait for page load event |
browser.waitForURL(pattern, { timeout }) | Wait until URL matches (string, glob with **, or RegExp) |
There is no separate fill() — use type(selector, text) or locator.type(text) to enter text. File upload is not covered in this guide; use CDP or evaluate if needed.
Assertions
CSTesting uses expect(value) with synchronous matchers. Get the value first (e.g. from the page), then assert.
Examples:
const title = await browser.evaluate("document.title");
expect(title).toContain('Example');
const html = await browser.content();
expect(html).toContain('Example Domain');
const url = await browser.url();
expect(url).toContain('example.com');
const text = await browser.locator('h1').textContent();
expect(text).toHaveLength(5);
Common matchers:
| Assertion | Description |
|---|---|
expect(x).toBe(y) | Strict equality (Object.is) |
expect(x).toEqual(y) | Deep equality (e.g. objects) |
expect(x).toBeTruthy() / toBeFalsy() | Boolean check |
expect(x).toBeNull() / toBeDefined() / toBeUndefined() | Null/undefined |
expect(x).toContain(item) | Array or string contains |
expect(x).toHaveLength(n) | Length of array or string |
expect(x).toBeGreaterThan(n) / toBeLessThan(n) | Numbers |
expect(fn).toThrow(message?) | Function throws |
expect(x).not.toBe(y) | Negate any matcher |
Element state (use with expect() on the returned value):
const visible = await browser.locator('button').isVisible();
expect(visible).toBe(true);
const disabled = await browser.locator('input').isDisabled();
expect(disabled).toBeFalsy();
const checked = await browser.locator('#agree').isSelected();
expect(checked).toBe(true);
const value = await browser.locator('#email').getAttribute('value');
expect(value).toEqual('user@test.com');
Test isolation
Each test runs in the same Node process, but you control browser isolation:
- One browser for the suite: Create the browser in
beforeAlland close it inafterAll(as in the first example). Tests share the same browser and page; navigate or reset state inbeforeEachif needed. - Fresh browser per test: Create and close the browser in
beforeEachandafterEachso each test gets a new browser and a clean profile.
describe('isolated tests', () => {
let browser;
beforeEach(async () => {
browser = await createBrowser({ headless: true });
});
afterEach(async () => {
if (browser) await browser.close();
});
it('first test', async () => {
await browser.goto('https://example.com');
// ...
});
it('second test', async () => {
// New browser; no leftover state from the first test.
await browser.goto('https://example.com');
// ...
});
});
Using test hooks
Use describe to group tests and hooks to run code before or after tests.
| Hook | When it runs |
|---|---|
beforeAll(fn) | Once before all tests in the suite |
afterAll(fn) | Once after all tests in the suite |
beforeEach(fn) | Before each test in the suite |
afterEach(fn) | After each test in the suite |
Example:
const { describe, it, expect, beforeAll, afterAll, beforeEach, createBrowser } = require('cstesting');
describe('navigation', () => {
let browser;
beforeAll(async () => {
browser = await createBrowser({ headless: true });
});
afterAll(async () => {
if (browser) await browser.close();
});
beforeEach(async () => {
await browser.goto('https://example.com');
});
it('starts on example.com', async () => {
const url = await browser.url();
expect(url).toContain('example.com');
});
it('has Example Domain text', async () => {
const html = await browser.content();
expect(html).toContain('Example Domain');
});
});
You can also use describe.only / it.only to run only that suite or test, and describe.skip / it.skip to skip them.