Use when adding new error messages to React, or seeing "unknown error code" warnings.
npx skills add berhalak/skills --skill "grist-nbrowser-tests"
Install specific skill from multi-skill repository
# Description
Use when writing, reading, or debugging nbrowser integration tests in the Grist codebase β Mocha + Selenium WebDriver tests in test/nbrowser/, using gristUtils, setupTestSuite, session management, cell interaction, or browser automation for Grist.
# SKILL.md
name: grist-nbrowser-tests
description: Use when writing, reading, or debugging nbrowser integration tests in the Grist codebase β Mocha + Selenium WebDriver tests in test/nbrowser/, using gristUtils, setupTestSuite, session management, cell interaction, or browser automation for Grist.
Grist nbrowser Tests
Integration tests using Mocha + mocha-webdriver + Selenium. Tests run against a real Grist server with a test database and a real browser.
Test File Template
import * as gu from "test/nbrowser/gristUtils";
import { server, setupTestSuite } from "test/nbrowser/testUtils";
import { assert, driver, Key } from "mocha-webdriver";
describe("MyFeature", function() {
this.timeout(20000);
const cleanup = setupTestSuite();
afterEach(() => gu.checkForErrors());
it("should do something", async function() {
const session = await gu.session().login();
const docId = await session.tempNewDoc(cleanup, "TestDoc");
await session.loadDoc(`/doc/${docId}`);
// Interact
await gu.getCell("A", 1).click();
await gu.enterCell("hello");
await gu.waitForServer();
// Assert
assert.equal(await gu.getCell("A", 1).getText(), "hello");
});
});
Key Patterns
| Pattern | How |
|---|---|
| Setup | const cleanup = setupTestSuite() at describe level |
| Login | gu.session().login() or server.simulateLogin(name, email, org) |
| Create temp doc | session.tempNewDoc(cleanup, "Name") β auto-cleaned |
| Load doc | session.loadDoc("/doc/" + docId) |
| Load from fixture | session.tempDoc(cleanup, "FixtureName.grist") |
| Cell interaction | gu.getCell(col, row), gu.enterCell(value), gu.sendKeys(...) |
| Wait for save | gu.waitForServer() after data-changing actions |
| Error checking | gu.checkForErrors() in afterEach |
| Bulk data setup | session.createHomeApi().applyUserActions(docId, actions) via API |
| Flaky waits | gu.waitToPass(async () => { ... }) for eventually-consistent checks |
| Undo | gu.undo() or gu.undo(count) |
gristUtils Quick Reference (gu)
Session & Navigation:
- gu.session() β returns Session.default (fluent builder)
- session.user("user1") β switch user
- session.teamSite, session.personalSite β predefined sites
- session.login(), session.loadDoc(path), session.loadDocMenu(path)
- session.createHomeApi() β get API client for direct data manipulation
Grid Cells:
- gu.getCell(col, row) β get cell element (col is name or 0-based index)
- gu.getVisibleGridCells({col, rowNums}) β read multiple cell values
- gu.enterCell(value) β type into active cell
- gu.getColumnNames() β list column headers
- gu.getGridRowCount() β count visible rows
UI Interaction:
- gu.sendKeys(...keys) β keyboard input
- gu.openColumnMenu(col, option?) β open column context menu
- gu.selectSectionByTitle(title) β switch active widget/section
- gu.addNewSection(type, table) β add a new widget
- gu.toggleSidePanel("right" | "left") β open/close panels
- gu.bigScreen() / gu.narrowScreen() β resize browser window
Data Actions (via API):
- gu.sendActions(actions) β send user actions to the document
- gu.sendCommand(name, args) β send a command
Waiting & Assertions:
- gu.waitForServer() β wait for pending requests to complete
- gu.waitForDocToLoad() β wait for document to fully load
- gu.waitToPass(fn, timeoutMs?) β retry fn until it passes
- gu.checkForErrors() β assert no unexpected console errors
Test Users
Predefined test users: "user1", "user2", "user3", "chimpy", "kiwi", "anon".
// Default session (chimpy)
const session = await gu.session().login();
// Specific user
const session = await gu.session().user("user1").login();
// Team site
const session = await gu.session().teamSite.login();
Data Setup Patterns
Empty doc with columns:
const docId = await session.tempNewDoc(cleanup, "Test");
const api = session.createHomeApi();
await api.applyUserActions(docId, [
["AddTable", "People", [{id: "Name"}, {id: "Age", type: "Int"}]],
["BulkAddRecord", "People", [null, null], {Name: ["Alice", "Bob"], Age: [30, 25]}],
]);
await session.loadDoc(`/doc/${docId}`);
From fixture:
const docId = await session.tempDoc(cleanup, "World.grist");
await session.loadDoc(`/doc/${docId}`);
Running Tests
# All nbrowser tests
./test/testrun.sh nbrowser
# Filter by test name
GREP_TESTS="MyFeature" ./test/testrun.sh nbrowser
# Debug mode (REPL on failure, screenshots)
DEBUG=1 ./test/testrun.sh nbrowser
# Verbose server output
VERBOSE=1 ./test/testrun.sh nbrowser
Server Lifecycle
setupTestSuite() handles everything:
1. Spawns real Grist server (devServerMain) with test SQLite DB
2. Assigns unique ports, connects testing hooks via Unix socket
3. After each suite: logs out, clears storage, closes extra windows
4. Screenshots and server logs captured on failure
Common Mistakes
| Mistake | Fix |
|---|---|
Forgetting waitForServer() after data change |
Always await it after clicks/edits that modify data |
Not using cleanup with tempNewDoc |
Pass cleanup so docs are auto-removed |
Missing afterEach(() => gu.checkForErrors()) |
Always include β catches silent JS errors |
| Hardcoding cell positions after row changes | Use gu.getVisibleGridCells to read actual state |
| Not waiting for UI transitions | Use gu.waitToPass() or driver.findWait(selector, ms) |
File Locations
- Tests:
test/nbrowser/*.ts - Core test utils:
core/test/nbrowser/gristUtils.ts,testUtils.ts - WebDriver utils:
core/test/nbrowser/gristWebDriverUtils.ts - Home/login utils:
core/test/nbrowser/homeUtil.ts - Test fixtures:
test/fixtures/ - Mocha config:
core/test/init-mocha-webdriver.js - Test runner:
test/testrun.sh
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.