docs/developer_docs/testing/e2e-testing.md
Apache Superset uses Playwright for end-to-end testing, migrating from the legacy Cypress tests.
cd superset-frontend
# Run all tests
npm run playwright:test
# or: npx playwright test
# Run specific test file
npx playwright test tests/auth/login.spec.ts
# Run with UI mode for debugging
npm run playwright:ui
# or: npx playwright test --ui
# Run in headed mode (see browser)
npm run playwright:headed
# or: npx playwright test --headed
# Debug specific test file
npm run playwright:debug tests/auth/login.spec.ts
# or: npx playwright test --debug tests/auth/login.spec.ts
Cypress tests are being migrated to Playwright. For legacy tests:
cd superset-frontend/cypress-base
npm run cypress-run-chrome # Headless
npm run cypress-debug # Interactive UI
superset-frontend/playwright/
├── components/core/ # Reusable UI components
├── pages/ # Page Object Models
├── tests/ # Test files organized by feature
├── utils/ # Shared constants and utilities
└── playwright.config.ts
We follow YAGNI (You Aren't Gonna Need It), DRY (Don't Repeat Yourself), and KISS (Keep It Simple, Stupid) principles:
Each page object encapsulates:
Example Page Object:
export class AuthPage {
// Selectors centralized in the page object
private static readonly SELECTORS = {
LOGIN_FORM: '[data-test="login-form"]',
USERNAME_INPUT: '[data-test="username-input"]',
} as const;
// Actions - what you can do
async loginWithCredentials(username: string, password: string) {}
// Queries - information you can get
async getCurrentUrl(): Promise<string> {}
// NO assertions - those belong in tests
}
Example Test:
import { test, expect } from '@playwright/test';
import { AuthPage } from '../../pages/AuthPage';
import { LOGIN } from '../../utils/urls';
test('should login with correct credentials', async ({ page }) => {
const authPage = new AuthPage(page);
await authPage.goto();
await authPage.loginWithCredentials('admin', 'general');
// Assertions belong in tests, not page objects
expect(await authPage.getCurrentUrl()).not.toContain(LOGIN);
});
Reusable UI interaction classes for common elements (components/core/):
fill(), type(), and pressSequentially() methodsUsage Example:
import { Form } from '../components/core';
const loginForm = new Form(page, '[data-test="login-form"]');
const usernameInput = loginForm.getInput('[data-test="username-input"]');
await usernameInput.fill('admin');
Playwright generates multiple reports for better visibility:
# View interactive HTML report (opens automatically on failure)
npm run playwright:report
# or: npx playwright show-report
# View test trace for debugging failures
npx playwright show-trace test-results/[test-name]/trace.zip
test-results/results.jsonWhen tests fail, Playwright automatically captures:
All debugging artifacts are available in the HTML report for easy analysis.
playwright.config.ts - matches Cypress settingshttp://localhost:8088 (assumes Superset running)feature.spec.tscomponents/core/index.ts for easy importingutils/urls.tsWhen porting Cypress tests:
cy.intercept/cy.wait with page.waitForRequest()utils/urls.tstests/auth/login.spec.tsutils/urls.ts