This guide outlines the systematic approach for migrating Cypress tests to Playwright in the n8n codebase, based on successful migrations and lessons learned.
- Follow the established 4-layer architecture: Tests β Composables β Page Objects β BasePage
- Use existing composables and page objects before creating new ones
- Maintain separation of concerns: business logic in composables, UI interactions in page objects
- ALWAYS search for existing Playwright patterns before implementing new functionality
- Look for working examples in existing test files (e.g.,
39-projects.spec.ts) - Check composables and page objects for existing methods
- Framework-specific patterns may differ (Cypress display names vs Playwright field names)
- Design tests to work regardless of initial state
- Use fresh project creation for tests that need empty states
- Create test prerequisites within the test when needed
- Avoid
@db:resetdependencies in favor of project-based isolation
# Start isolated test environment
cd packages/testing/playwright
pnpm start:isolated
# Run tests with proper environment
N8N_BASE_URL=http://localhost:5679 npx playwright test --reporter=list- Review
CONTRIBUTING.mdfor architecture guidelines - Examine working test files (e.g.,
1-workflows.spec.ts,39-projects.spec.ts) - Check available composables in
composables/directory - Review page objects in
pages/directory
- Cypress: Uses display names (
'Internal Integration Secret') - Playwright: Uses field names (
'apiKey') - Navigation: Direct page navigation often more reliable than complex UI interactions
- Selectors: Prefer
data-test-idover text-based selectors
// 1. Create test file with proper imports
import { test, expect } from '../fixtures/base';
import {
// Import constants from existing patterns
NOTION_NODE_NAME,
NEW_NOTION_ACCOUNT_NAME,
// ... other constants
} from '../config/constants';
// 2. Add beforeEach setup if needed
test.describe('Feature Name', () => {
test.beforeEach(async ({ api, n8n }) => {
await api.enableFeature('sharing');
await api.enableFeature('folders');
// ... other feature flags
await n8n.goHome();
});
// 3. Scaffold all tests from Cypress file
test('should do something', async ({ n8n }) => {
// TODO: Implement based on Cypress version
console.log('Test scaffolded - ready for implementation');
});
});# Search for existing implementations
grep -r "addCredentialToProject" packages/testing/playwright/
grep -r "createProject" packages/testing/playwright/
grep -r "workflowComposer" packages/testing/playwright/- Start with tests that have clear existing patterns
- Use composables for high-level operations (project creation, navigation)
- Use direct DOM interactions for form filling when composables don't match
- Implement one test at a time and verify it works
- Node Creation Issues: Close NDV after adding first node to prevent overlay blocking
- Universal Add Button: Use direct navigation when button interactions fail
- Modal Overlays: Use route interception for error testing
- Multiple Elements: Use specific selectors to avoid strict mode violations
// β
Good: Use existing composable
const { projectName } = await n8n.projectComposer.createProject();
await n8n.projectComposer.addCredentialToProject(
projectName,
'Notion API',
'apiKey', // Use field name, not display name
'test_value'
);// β
Good: Direct navigation when UI interactions fail
await n8n.page.goto('/home/credentials/create');
await n8n.page.goto('/workflow/new');// β
Good: Force errors for notification testing
await n8n.page.route('**/rest/credentials', route => {
route.abort();
});// β
Good: Handle NDV auto-opening
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME);
await n8n.ndv.close(); // Close NDV that opens automatically
await n8n.canvas.addNode(NOTION_NODE_NAME);// β Bad: Implementing without checking existing patterns
await n8n.page.getByText('Internal Integration Secret').fill('value');
// β
Good: Use existing composable with correct field name
await n8n.projectComposer.addCredentialToProject(
projectName, 'Notion API', 'apiKey', 'value'
);// β Bad: Assuming Cypress patterns work in Playwright
await n8n.credentialsModal.connectionParameter('Internal Integration Secret').fill('value');
// β
Good: Use Playwright field names
await n8n.page.getByTestId('parameter-input-field').fill('value');// β Bad: Complex button clicking when direct navigation works
await n8n.workflows.clickAddWorkflowButton();
await n8n.page.waitForLoadState();
// β
Good: Direct navigation
await n8n.page.goto('/workflow/new');
await n8n.page.waitForLoadState('networkidle');// β Bad: Not handling NDV auto-opening
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME);
await n8n.canvas.addNode(NOTION_NODE_NAME); // This will fail
// β
Good: Close NDV after first node
await n8n.canvas.addNode(SCHEDULE_TRIGGER_NODE_NAME);
await n8n.ndv.close();
await n8n.canvas.addNode(NOTION_NODE_NAME);- Begin with basic navigation and page verification tests
- Use existing composables for common operations
- Verify each test works before moving to complex scenarios
- Scaffold all tests first with placeholders
- Implement one test at a time
- Use
console.logfor placeholder tests to maintain passing test suite
// Add pauses for debugging
await n8n.page.pause();
// Use headed mode for visual debugging
SHOW_BROWSER=true npx playwright test
// Use specific test selection
npx playwright test -g "test name" --reporter=list- Run individual tests during development
- Run full test suite after each major change
- Use
--reporter=listfor clear output during development
{
"playwright.env": {
"N8N_BASE_URL": "http://localhost:5679",
"SHOW_BROWSER": "true",
"RESET_E2E_DB": "true"
}
}{
"scripts": {
"start:isolated": "cd ..; N8N_PORT=5679 N8N_USER_FOLDER=/tmp/n8n-test-$(date +%s) E2E_TESTS=true pnpm start",
"test:local": "RESET_E2E_DB=true N8N_BASE_URL=http://localhost:5679 start-server-and-test 'pnpm start:isolated' http://localhost:5679/favicon.ico 'sleep 1 && pnpm test:standard --workers 4 --repeat-each 5'"
}
}- All tests from Cypress file are scaffolded
- All tests pass consistently
- Tests use existing composables where appropriate
- Tests follow established patterns
- No
@db:resetdependencies (unless absolutely necessary) - Tests are idempotent and can run in any order
- Complex UI interactions are handled properly
- Tests use proper error handling
- Tests include appropriate assertions
- Tests follow naming conventions
- Tests include proper comments
- Tests use constants for repeated values
- Tests handle dynamic data properly
- Search First: Always look for existing patterns before implementing
- Use Composables: Leverage existing business logic composables
- Direct Navigation: Prefer direct page navigation over complex UI interactions
- Handle UI Blocking: Close modals/NDV when adding multiple nodes
- Framework Awareness: Understand differences between Cypress and Playwright
- Incremental Approach: Implement one test at a time
- Idempotent Design: Make tests work regardless of initial state
- Proper Debugging: Use pauses and headed mode for troubleshooting