Skip to content

Getting started

Sounding is designed to feel natural in a Sails app from the first install.

1. Install Sounding

bash
npm install -D sounding @playwright/test sails-sqlite

2. Set up your test environment

Sounding does not replace Sails test config.

Your app still defines its test environment in config/env/test.js, and Sounding builds on top of that.

A strong default is to let Sounding manage the datastore and keep config/env/test.js focused on app behavior.

js
// config/env/test.js
module.exports = {
  port: 3333,

  models: {
    migrate: 'drop'
  },

  mail: {
    default: 'log'
  },

  log: {
    level: 'error'
  }
}

If your app already has a good test datastore strategy, Sounding can still respect it through datastore.mode = 'inherit'.

3. Add config/sounding.js only if you need overrides

js
module.exports.sounding = {
  datastore: 'inherit'
}

Most apps can skip this file entirely. Sounding already defaults to:

  • datastore.mode = 'managed'
  • datastore.identity = 'default'
  • datastore.adapter = 'sails-sqlite'
  • datastore.root = '.tmp/db'
  • datastore.isolation = 'worker'
  • mail.capture = true
  • request.transport = 'virtual'
  • browser projects start with desktop

Only add config/sounding.js when your app needs a real override, such as datastore: 'inherit', datastore: 'external', or custom browser behavior.

4. Write your first trial

If you are new to Sounding's callback shape, read Trials and Trial context alongside this guide. They explain what a trial is, what arrives inside test(), and when things like page and login appear.

js
import { test } from 'sounding'

test('signupWithTeam creates a team and membership', async ({
  sails,
  expect
}) => {
  const result = await sails.helpers.user.signupWithTeam({
    fullName: 'Kelvin O',
    email: '[email protected]',
    tosAcceptedByIp: '127.0.0.1'
  })

  expect(result.user.email).toBe('[email protected]')
  expect(result.team.name).toBeDefined()
})

5. Use the same trial surface for endpoints

js
import { test } from 'sounding'

test('guest is redirected from the dashboard', async ({ get, expect }) => {
  const response = await get('/dashboard')

  expect(response).toRedirectTo('/login')
})

6. Write your first browser trial

js
import { test } from 'sounding'

test(
  'subscriber can read a members-only issue',
  { browser: true },
  async ({ sails, page, login, expect }) => {
    await sails.sounding.world.use('issue-access')
    await login.as('subscriber', page)

    await page.goto('/issues/the-nerve-to-build')

    await expect(page.getByText('The rest of the story')).toBeVisible()
  }
)

6.1 Define your first world

Before a suite gets interesting, give it a named business situation to stand on.

A good first world is usually one of:

  • a signed-out guest
  • a subscriber with active access
  • a publisher with a draft
  • a reader waiting on a magic link

For example:

js
import { defineScenario } from 'sounding'

export default defineScenario('issue-access', async ({ create }) => {
  const publisher = await create('user').trait('publisher')
  const subscriber = await create('user').trait('subscriber')
  const issue = await create('issue', {
    author: publisher.id,
    isFree: false
  })

  return {
    users: { publisher, subscriber },
    issues: { gatedIssue: issue }
  }
})

Then the trial can read from the product situation directly:

js
test('subscriber can read the issue', async ({ sails, expect }) => {
  const current = await sails.sounding.world.use('issue-access')

  const response = await sails.sounding.request
    .as(current.users.subscriber)
    .get(`/i/${current.issues.gatedIssue.slug}`)

  expect(response).toHaveStatus(200)
})

This is the point of worlds: the test stops reading like setup and starts reading like behavior.

7. Run the suite

bash
npm run test

What to add next

Once the basic runtime is in place, most apps will want to add:

  • factories under tests/factories
  • scenarios under tests/scenarios
  • a few named actors like guest, subscriber, and publisher
  • at least one endpoint, Inertia, and browser trial for a mission-critical flow
  • a mobile browser project once the core journeys are stable

Sounding works best when your tests read like your product.

All open source projects are released under the MIT License.