/playwright
Write or update Playwright E2E tests following project conventions
$ npx -y skills add nimbalyst/nimbalyst --agent claude-codeHow it fires
How this command gets triggered: by you, by Claude, or both.
- Fires itselfClaude auto-loads it when your prompt matches the work.
- You can call itInvoke it directly when you want it.
- Slash command
/playwright
Context preview
What this command does when you run it.
Write or update Playwright E2E tests following project conventions
Command definition
playwright.mdname: playwright
description: Write or update Playwright E2E tests following project conventions
Playwright E2E Test Instructions
You are writing Playwright E2E tests for the Nimbalyst editor. Follow these rules STRICTLY.
Critical Rules
1. NEVER Hardcode Selectors
Import and use selectors from `PLAYWRIGHT_TEST_SELECTORS` in `e2e/utils/testHelpers.ts`.
// BAD - NEVER DO THIS
await page.locator('.tab-dirty-indicator').toBeVisible();
await page.locator('[contenteditable="true"]').click();
// GOOD - Use shared selectors AND target by document path
import { PLAYWRIGHT_TEST_SELECTORS, getTabByFileName } from '../utils/testHelpers';
const tab = getTabByFileName(page, 'test.md');
await expect(tab.locator(PLAYWRIGHT_TEST_SELECTORS.tabDirtyIndicator)).toBeVisible();
// For editors, scope to the specific file's editor
const editor = page.locator(`[data-filepath="${filePath}"]`);
await editor.locator(PLAYWRIGHT_TEST_SELECTORS.contentEditable).click();If a selector doesn't exist in `PLAYWRIGHT_TEST_SELECTORS`, ADD IT THERE first.
2. NEVER Use `.first()` as a Lazy Escape Hatch
Nimbalyst is a complex app with multiple windows, multiple editor tabs, multiple AI sessions, etc. You MUST target elements precisely.
// BAD - Lazy, will break with multiple tabs/editors
await page.locator('.tab').first().click();
await page.locator('.monaco-editor').first().type('hello');
await page.locator('[contenteditable="true"]').first().click();
// GOOD - Target precisely using data attributes
await page.locator('[data-testid="tab"][data-filepath="/path/to/file.md"]').click();
await page.locator('[data-testid="monaco-editor"][data-filepath="/path/to/file.ts"]').type('hello');**If you can't target an element precisely:** 1. ADD a `data-testid` attribute to the component 2. ADD a `data-filepath`, `data-session-id`, or other identifying data attribute 3. Update `PLAYWRIGHT_TEST_SELECTORS` with the new selector 4. THEN write the test
Common data attributes we use:
- `data-testid` - Unique identifier for test targeting
- `data-filepath` - Full path to file for editors/tabs
- `data-filename` - Filename for simpler cases
- `data-session-id` - AI session identifier
- `data-tab-type` - "document" or "session"
- `data-active` - "true"/"false" for active state
3. Use Test Helpers
Check `e2e/utils/testHelpers.ts` and `e2e/helpers.ts` for existing utilities BEFORE writing inline code.
// BAD - Inline implementation
await page.locator('.file-tree-name', { hasText: 'test.md' }).click();
await expect(page.locator('.tab', { hasText: 'test.md' })).toBeVisible({ timeout: 3000 });
// GOOD - Use helper
import { openFileFromTree } from '../utils/testHelpers';
await openFileFromTree(page, 'test.md');Available helpers include:
- `openFileFromTree(page, fileName)`
- `manualSaveDocument(page)`
- `waitForAutosave(page, fileName)`
- `dismissAPIKeyDialog(page)`
- `waitForWorkspaceReady(page)`
- `switchToAgentMode(page)`, `switchToFilesMode(page)`
- `editDocumentContent(page, editor, content)`
- `openHistoryDialog(page)`, `restoreFromHistory(page)`
- `closeTabByFileName(page, fileName)`
- `getTabByFileName(page, fileName)`
4. Write ONE Test First, Get It Working
Write ONE test case and get it passing before writing more. Do NOT write 10 tests that all fail because the first one didn't even load correctly.
// BAD - Writing many tests before verifying any work
test('should open file', async () => { /* ... */ });
test('should edit file', async () => { /* ... */ });
test('should save file', async () => { /* ... */ });
test('should show history', async () => { /* ... */ });
// All 4 fail because app didn't even launch properly
// GOOD - Write one test, run it, fix it, then add more
test('complete file editing workflow', async () => {
// Start with just the first step, verify it works
await openFileFromTree(page, 'test.md');
// Once this works, add more steps...
});5. Write Sequential Tests, Not Incremental
Write ONE test that performs a complete workflow sequentially. Do NOT write multiple small tests that each test one tiny step.
// BAD - Too many incremental tests
test('should open file', async () => { /* ... */ });
test('should show dirty indicator after edit', async () => { /* ... */ });
test('should save file', async () => { /* ... */ });
test('should clear dirty indicator after save', async () => { /* ... */ });
// GOOD - One sequential test covering the workflow
test('should open file, edit, save, and clear dirty indicator', async () => {
await openFileFromTree(page, 'test.md');
await editDocumentContent(page, editor, 'new content');
await expect(tab.locator(PLAYWRIGHT_TEST_SELECTORS.tabDirtyIndicator)).toBeVisible();
await manualSaveDocument(page);
await expect(tab.locator(PLAYWRIGHT_TEST_SELECTORS.tabDirtyIndicator)).not.toBeVisible();
});6. Minimize Timeouts and Waits
Use Playwright's built-in waiting (expect with timeout, waitForSelector) instead of arbitrary `waitForTimeout()`.
// BAD - Arbitrary timeout
await page.waitForTimeout(2000);
const content = await fs.readFile(filePath, 'utf8');
// GOOD - Wait for specific condition
await expect(tab.locator(PLAYWRIGHT_TEST_SELECTORS.tabDirtyIndicator)).not.toBeVisible({ timeout: 3000 });
const content = await fs.readFile(filePath, 'utf8');Only use `waitForTimeout()` when there is NO other option (e.g., waiting for debounced operations).
7. Create Files BEFORE Launching App
test.beforeEach(async () => {
workspaceDir = await createTempWorkspace();
// CORRECT: Create files BEFORE launch
await fs.writeFile(path.join(workspaceDir, 'test.md'), '# Test\n', 'utf8');
electronApp = await launchElectronApp({ workspace: workspaceDir });
page = await electronApp.firstWindow();
await waitForAppReady(page);
});8. Use `launchElectronApp` Options Correctly
// Fo
Read more
name: playwright description: Write or update Playwright E2E tests following project conventions
Playwright E2E Test Instructions
You are writing Playwright E2E tests for the Nimbalyst editor. Follow these rules STRICTLY.
Critical Rules
1. NEVER Hardcode Selectors
Import and use selectors from `PLAYWRIGHT_TEST_SELECTORS` in `e2e/utils/testHelpers.ts`.
// BAD - NEVER DO THIS
await page.locator('.tab-dirty-indicator').toBeVisible();
await page.locator('[contenteditable="true"]').click();
// GOOD - Use shared selectors AND target by document path
import { PLAYWRIGHT_TEST_SELECTORS, getTabByFileName } from '../utils/testHelpers';
const tab = getTabByFileName(page, 'test.md');
await expect(tab.locator(PLAYWRIGHT_TEST_SELECTORS.tabDirtyIndicator)).toBeVisible();
// For editors, scope to the specific file's editor
const editor = page.locator(`[data-filepath="${filePath}"]`);
await editor.locator(PLAYWRIGHT_TEST_SELECTORS.contentEditable).click();If a selector doesn't exist in `PLAYWRIGHT_TEST_SELECTORS`, ADD IT THERE first.
2. NEVER Use `.first()` as a Lazy Escape Hatch
Nimbalyst is a complex app with multiple windows, multiple editor tabs, multiple AI sessions, etc. You MUST target elements precisely.
// BAD - Lazy, will break with multiple tabs/editors
await page.locator('.tab').first().click();
await page.locator('.monaco-editor').first().type('hello');
await page.locator('[contenteditable="true"]').first().click();
// GOOD - Target precisely using data attributes
await page.locator('[data-testid="tab"][data-filepath="/path/to/file.md"]').click();
await page.locator('[data-testid="monaco-editor"][data-filepath="/path/to/file.ts"]').type('hello');**If you can't target an element precisely:** 1. ADD a `data-testid` attribute to the component 2. ADD a `data-filepath`, `data-session-id`, or other identifying data attribute 3. Update `PLAYWRIGHT_TEST_SELECTORS` with the new selector 4. THEN write the test
Common data attributes we use:
- `data-testid` - Unique identifier for test targeting
- `data-filepath` - Full path to file for editors/tabs
- `data-filename` - Filename for simpler cases
- `data-session-id` - AI session identifier
- `data-tab-type` - "document" or "session"
- `data-active` - "true"/"false" for active state
3. Use Test Helpers
Check `e2e/utils/testHelpers.ts` and `e2e/helpers.ts` for existing utilities BEFORE writing inline code.
// BAD - Inline implementation
await page.locator('.file-tree-name', { hasText: 'test.md' }).click();
await expect(page.locator('.tab', { hasText: 'test.md' })).toBeVisible({ timeout: 3000 });
// GOOD - Use helper
import { openFileFromTree } from '../utils/testHelpers';
await openFileFromTree(page, 'test.md');Available helpers include:
- `openFileFromTree(page, fileName)`
- `manualSaveDocument(page)`
- `waitForAutosave(page, fileName)`
- `dismissAPIKeyDialog(page)`
- `waitForWorkspaceReady(page)`
- `switchToAgentMode(page)`, `switchToFilesMode(page)`
- `editDocumentContent(page, editor, content)`
- `openHistoryDialog(page)`, `restoreFromHistory(page)`
- `closeTabByFileName(page, fileName)`
- `getTabByFileName(page, fileName)`
4. Write ONE Test First, Get It Working
Write ONE test case and get it passing before writing more. Do NOT write 10 tests that all fail because the first one didn't even load correctly.
// BAD - Writing many tests before verifying any work
test('should open file', async () => { /* ... */ });
test('should edit file', async () => { /* ... */ });
test('should save file', async () => { /* ... */ });
test('should show history', async () => { /* ... */ });
// All 4 fail because app didn't even launch properly
// GOOD - Write one test, run it, fix it, then add more
test('complete file editing workflow', async () => {
// Start with just the first step, verify it works
await openFileFromTree(page, 'test.md');
// Once this works, add more steps...
});5. Write Sequential Tests, Not Incremental
Write ONE test that performs a complete workflow sequentially. Do NOT write multiple small tests that each test one tiny step.
// BAD - Too many incremental tests
test('should open file', async () => { /* ... */ });
test('should show dirty indicator after edit', async () => { /* ... */ });
test('should save file', async () => { /* ... */ });
test('should clear dirty indicator after save', async () => { /* ... */ });
// GOOD - One sequential test covering the workflow
test('should open file, edit, save, and clear dirty indicator', async () => {
await openFileFromTree(page, 'test.md');
await editDocumentContent(page, editor, 'new content');
await expect(tab.locator(PLAYWRIGHT_TEST_SELECTORS.tabDirtyIndicator)).toBeVisible();
await manualSaveDocument(page);
await expect(tab.locator(PLAYWRIGHT_TEST_SELECTORS.tabDirtyIndicator)).not.toBeVisible();
});6. Minimize Timeouts and Waits
Use Playwright's built-in waiting (expect with timeout, waitForSelector) instead of arbitrary `waitForTimeout()`.
// BAD - Arbitrary timeout
await page.waitForTimeout(2000);
const content = await fs.readFile(filePath, 'utf8');
// GOOD - Wait for specific condition
await expect(tab.locator(PLAYWRIGHT_TEST_SELECTORS.tabDirtyIndicator)).not.toBeVisible({ timeout: 3000 });
const content = await fs.readFile(filePath, 'utf8');Only use `waitForTimeout()` when there is NO other option (e.g., waiting for debounced operations).
7. Create Files BEFORE Launching App
test.beforeEach(async () => {
workspaceDir = await createTempWorkspace();
// CORRECT: Create files BEFORE launch
await fs.writeFile(path.join(workspaceDir, 'test.md'), '# Test\n', 'utf8');
electronApp = await launchElectronApp({ workspace: workspaceDir });
page = await electronApp.firstWindow();
await waitForAppReady(page);
});8. Use `launchElectronApp` Options Correctly
// Fo
Nimbalyst is a free, open-source, local, interactive visual editor & session/task manager for developers, product managers, designers, builders.
Repo: nimbalyst/nimbalyst
Other commands on nimbalyst.
- /android-release
Prepare and execute an Android release (patch/minor/major)
Open command - /bug-report
Gather details and draft an actionable bug report for developers.
Open command - /commit
Create a git commit with concise, bullet-point commit message
Open command - /design
Create a new plan document for tracking work.
Open command - /e2e-devcontainer
Run E2E tests in a dev container (isolated environment)
Open command - /implement
Execute a plan document while keeping progress synchronized.
Open command

