Get started

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

ActionDescription
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:

AssertionDescription
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 beforeAll and close it in afterAll (as in the first example). Tests share the same browser and page; navigate or reset state in beforeEach if needed.
  • Fresh browser per test: Create and close the browser in beforeEach and afterEach so 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.

HookWhen 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.